diff options
author | uzhas <uzhas@ydb.tech> | 2023-11-16 16:04:50 +0300 |
---|---|---|
committer | uzhas <uzhas@ydb.tech> | 2023-11-16 17:46:46 +0300 |
commit | 46f0c0079bb50609d2eeb6586642bcf114fc5239 (patch) | |
tree | 84e4e4978d57fe5de321ba69bf9d0c290de60a66 /vendor/go.uber.org | |
parent | 73045e389397816cc2bdd6cd7818b4bce427b265 (diff) | |
download | ydb-46f0c0079bb50609d2eeb6586642bcf114fc5239.tar.gz |
enable ya make for go projects
Diffstat (limited to 'vendor/go.uber.org')
194 files changed, 24743 insertions, 0 deletions
diff --git a/vendor/go.uber.org/atomic/bool.go b/vendor/go.uber.org/atomic/bool.go new file mode 100644 index 0000000000..f0a2ddd148 --- /dev/null +++ b/vendor/go.uber.org/atomic/bool.go @@ -0,0 +1,88 @@ +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" +) + +// Bool is an atomic type-safe wrapper for bool values. +type Bool struct { + _ nocmp // disallow non-atomic comparison + + v Uint32 +} + +var _zeroBool bool + +// NewBool creates a new Bool. +func NewBool(val bool) *Bool { + x := &Bool{} + if val != _zeroBool { + x.Store(val) + } + return x +} + +// Load atomically loads the wrapped bool. +func (x *Bool) Load() bool { + return truthy(x.v.Load()) +} + +// Store atomically stores the passed bool. +func (x *Bool) Store(val bool) { + x.v.Store(boolToInt(val)) +} + +// CAS is an atomic compare-and-swap for bool values. +// +// Deprecated: Use CompareAndSwap. +func (x *Bool) CAS(old, new bool) (swapped bool) { + return x.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap for bool values. +func (x *Bool) CompareAndSwap(old, new bool) (swapped bool) { + return x.v.CompareAndSwap(boolToInt(old), boolToInt(new)) +} + +// Swap atomically stores the given bool and returns the old +// value. +func (x *Bool) Swap(val bool) (old bool) { + return truthy(x.v.Swap(boolToInt(val))) +} + +// MarshalJSON encodes the wrapped bool into JSON. +func (x *Bool) MarshalJSON() ([]byte, error) { + return json.Marshal(x.Load()) +} + +// UnmarshalJSON decodes a bool from JSON. +func (x *Bool) UnmarshalJSON(b []byte) error { + var v bool + if err := json.Unmarshal(b, &v); err != nil { + return err + } + x.Store(v) + return nil +} diff --git a/vendor/go.uber.org/atomic/bool_ext.go b/vendor/go.uber.org/atomic/bool_ext.go new file mode 100644 index 0000000000..a2e60e9873 --- /dev/null +++ b/vendor/go.uber.org/atomic/bool_ext.go @@ -0,0 +1,53 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "strconv" +) + +//go:generate bin/gen-atomicwrapper -name=Bool -type=bool -wrapped=Uint32 -pack=boolToInt -unpack=truthy -cas -swap -json -file=bool.go + +func truthy(n uint32) bool { + return n == 1 +} + +func boolToInt(b bool) uint32 { + if b { + return 1 + } + return 0 +} + +// Toggle atomically negates the Boolean and returns the previous value. +func (b *Bool) Toggle() (old bool) { + for { + old := b.Load() + if b.CAS(old, !old) { + return old + } + } +} + +// String encodes the wrapped value as a string. +func (b *Bool) String() string { + return strconv.FormatBool(b.Load()) +} diff --git a/vendor/go.uber.org/atomic/doc.go b/vendor/go.uber.org/atomic/doc.go new file mode 100644 index 0000000000..ae7390ee68 --- /dev/null +++ b/vendor/go.uber.org/atomic/doc.go @@ -0,0 +1,23 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package atomic provides simple wrappers around numerics to enforce atomic +// access. +package atomic diff --git a/vendor/go.uber.org/atomic/duration.go b/vendor/go.uber.org/atomic/duration.go new file mode 100644 index 0000000000..7c23868fc8 --- /dev/null +++ b/vendor/go.uber.org/atomic/duration.go @@ -0,0 +1,89 @@ +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" + "time" +) + +// Duration is an atomic type-safe wrapper for time.Duration values. +type Duration struct { + _ nocmp // disallow non-atomic comparison + + v Int64 +} + +var _zeroDuration time.Duration + +// NewDuration creates a new Duration. +func NewDuration(val time.Duration) *Duration { + x := &Duration{} + if val != _zeroDuration { + x.Store(val) + } + return x +} + +// Load atomically loads the wrapped time.Duration. +func (x *Duration) Load() time.Duration { + return time.Duration(x.v.Load()) +} + +// Store atomically stores the passed time.Duration. +func (x *Duration) Store(val time.Duration) { + x.v.Store(int64(val)) +} + +// CAS is an atomic compare-and-swap for time.Duration values. +// +// Deprecated: Use CompareAndSwap. +func (x *Duration) CAS(old, new time.Duration) (swapped bool) { + return x.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap for time.Duration values. +func (x *Duration) CompareAndSwap(old, new time.Duration) (swapped bool) { + return x.v.CompareAndSwap(int64(old), int64(new)) +} + +// Swap atomically stores the given time.Duration and returns the old +// value. +func (x *Duration) Swap(val time.Duration) (old time.Duration) { + return time.Duration(x.v.Swap(int64(val))) +} + +// MarshalJSON encodes the wrapped time.Duration into JSON. +func (x *Duration) MarshalJSON() ([]byte, error) { + return json.Marshal(x.Load()) +} + +// UnmarshalJSON decodes a time.Duration from JSON. +func (x *Duration) UnmarshalJSON(b []byte) error { + var v time.Duration + if err := json.Unmarshal(b, &v); err != nil { + return err + } + x.Store(v) + return nil +} diff --git a/vendor/go.uber.org/atomic/duration_ext.go b/vendor/go.uber.org/atomic/duration_ext.go new file mode 100644 index 0000000000..4c18b0a9ed --- /dev/null +++ b/vendor/go.uber.org/atomic/duration_ext.go @@ -0,0 +1,40 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import "time" + +//go:generate bin/gen-atomicwrapper -name=Duration -type=time.Duration -wrapped=Int64 -pack=int64 -unpack=time.Duration -cas -swap -json -imports time -file=duration.go + +// Add atomically adds to the wrapped time.Duration and returns the new value. +func (d *Duration) Add(delta time.Duration) time.Duration { + return time.Duration(d.v.Add(int64(delta))) +} + +// Sub atomically subtracts from the wrapped time.Duration and returns the new value. +func (d *Duration) Sub(delta time.Duration) time.Duration { + return time.Duration(d.v.Sub(int64(delta))) +} + +// String encodes the wrapped value as a string. +func (d *Duration) String() string { + return d.Load().String() +} diff --git a/vendor/go.uber.org/atomic/error.go b/vendor/go.uber.org/atomic/error.go new file mode 100644 index 0000000000..b7e3f1291a --- /dev/null +++ b/vendor/go.uber.org/atomic/error.go @@ -0,0 +1,72 @@ +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +// Error is an atomic type-safe wrapper for error values. +type Error struct { + _ nocmp // disallow non-atomic comparison + + v Value +} + +var _zeroError error + +// NewError creates a new Error. +func NewError(val error) *Error { + x := &Error{} + if val != _zeroError { + x.Store(val) + } + return x +} + +// Load atomically loads the wrapped error. +func (x *Error) Load() error { + return unpackError(x.v.Load()) +} + +// Store atomically stores the passed error. +func (x *Error) Store(val error) { + x.v.Store(packError(val)) +} + +// CompareAndSwap is an atomic compare-and-swap for error values. +func (x *Error) CompareAndSwap(old, new error) (swapped bool) { + if x.v.CompareAndSwap(packError(old), packError(new)) { + return true + } + + if old == _zeroError { + // If the old value is the empty value, then it's possible the + // underlying Value hasn't been set and is nil, so retry with nil. + return x.v.CompareAndSwap(nil, packError(new)) + } + + return false +} + +// Swap atomically stores the given error and returns the old +// value. +func (x *Error) Swap(val error) (old error) { + return unpackError(x.v.Swap(packError(val))) +} diff --git a/vendor/go.uber.org/atomic/error_ext.go b/vendor/go.uber.org/atomic/error_ext.go new file mode 100644 index 0000000000..d31fb633bb --- /dev/null +++ b/vendor/go.uber.org/atomic/error_ext.go @@ -0,0 +1,39 @@ +// Copyright (c) 2020-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +// atomic.Value panics on nil inputs, or if the underlying type changes. +// Stabilize by always storing a custom struct that we control. + +//go:generate bin/gen-atomicwrapper -name=Error -type=error -wrapped=Value -pack=packError -unpack=unpackError -compareandswap -swap -file=error.go + +type packedError struct{ Value error } + +func packError(v error) interface{} { + return packedError{v} +} + +func unpackError(v interface{}) error { + if err, ok := v.(packedError); ok { + return err.Value + } + return nil +} diff --git a/vendor/go.uber.org/atomic/float32.go b/vendor/go.uber.org/atomic/float32.go new file mode 100644 index 0000000000..62c36334fd --- /dev/null +++ b/vendor/go.uber.org/atomic/float32.go @@ -0,0 +1,77 @@ +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" + "math" +) + +// Float32 is an atomic type-safe wrapper for float32 values. +type Float32 struct { + _ nocmp // disallow non-atomic comparison + + v Uint32 +} + +var _zeroFloat32 float32 + +// NewFloat32 creates a new Float32. +func NewFloat32(val float32) *Float32 { + x := &Float32{} + if val != _zeroFloat32 { + x.Store(val) + } + return x +} + +// Load atomically loads the wrapped float32. +func (x *Float32) Load() float32 { + return math.Float32frombits(x.v.Load()) +} + +// Store atomically stores the passed float32. +func (x *Float32) Store(val float32) { + x.v.Store(math.Float32bits(val)) +} + +// Swap atomically stores the given float32 and returns the old +// value. +func (x *Float32) Swap(val float32) (old float32) { + return math.Float32frombits(x.v.Swap(math.Float32bits(val))) +} + +// MarshalJSON encodes the wrapped float32 into JSON. +func (x *Float32) MarshalJSON() ([]byte, error) { + return json.Marshal(x.Load()) +} + +// UnmarshalJSON decodes a float32 from JSON. +func (x *Float32) UnmarshalJSON(b []byte) error { + var v float32 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + x.Store(v) + return nil +} diff --git a/vendor/go.uber.org/atomic/float32_ext.go b/vendor/go.uber.org/atomic/float32_ext.go new file mode 100644 index 0000000000..b0cd8d9c82 --- /dev/null +++ b/vendor/go.uber.org/atomic/float32_ext.go @@ -0,0 +1,76 @@ +// Copyright (c) 2020-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "math" + "strconv" +) + +//go:generate bin/gen-atomicwrapper -name=Float32 -type=float32 -wrapped=Uint32 -pack=math.Float32bits -unpack=math.Float32frombits -swap -json -imports math -file=float32.go + +// Add atomically adds to the wrapped float32 and returns the new value. +func (f *Float32) Add(delta float32) float32 { + for { + old := f.Load() + new := old + delta + if f.CAS(old, new) { + return new + } + } +} + +// Sub atomically subtracts from the wrapped float32 and returns the new value. +func (f *Float32) Sub(delta float32) float32 { + return f.Add(-delta) +} + +// CAS is an atomic compare-and-swap for float32 values. +// +// Deprecated: Use CompareAndSwap +func (f *Float32) CAS(old, new float32) (swapped bool) { + return f.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap for float32 values. +// +// Note: CompareAndSwap handles NaN incorrectly. NaN != NaN using Go's inbuilt operators +// but CompareAndSwap allows a stored NaN to compare equal to a passed in NaN. +// This avoids typical CompareAndSwap loops from blocking forever, e.g., +// +// for { +// old := atom.Load() +// new = f(old) +// if atom.CompareAndSwap(old, new) { +// break +// } +// } +// +// If CompareAndSwap did not match NaN to match, then the above would loop forever. +func (f *Float32) CompareAndSwap(old, new float32) (swapped bool) { + return f.v.CompareAndSwap(math.Float32bits(old), math.Float32bits(new)) +} + +// String encodes the wrapped value as a string. +func (f *Float32) String() string { + // 'g' is the behavior for floats with %v. + return strconv.FormatFloat(float64(f.Load()), 'g', -1, 32) +} diff --git a/vendor/go.uber.org/atomic/float64.go b/vendor/go.uber.org/atomic/float64.go new file mode 100644 index 0000000000..5bc11caabe --- /dev/null +++ b/vendor/go.uber.org/atomic/float64.go @@ -0,0 +1,77 @@ +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" + "math" +) + +// Float64 is an atomic type-safe wrapper for float64 values. +type Float64 struct { + _ nocmp // disallow non-atomic comparison + + v Uint64 +} + +var _zeroFloat64 float64 + +// NewFloat64 creates a new Float64. +func NewFloat64(val float64) *Float64 { + x := &Float64{} + if val != _zeroFloat64 { + x.Store(val) + } + return x +} + +// Load atomically loads the wrapped float64. +func (x *Float64) Load() float64 { + return math.Float64frombits(x.v.Load()) +} + +// Store atomically stores the passed float64. +func (x *Float64) Store(val float64) { + x.v.Store(math.Float64bits(val)) +} + +// Swap atomically stores the given float64 and returns the old +// value. +func (x *Float64) Swap(val float64) (old float64) { + return math.Float64frombits(x.v.Swap(math.Float64bits(val))) +} + +// MarshalJSON encodes the wrapped float64 into JSON. +func (x *Float64) MarshalJSON() ([]byte, error) { + return json.Marshal(x.Load()) +} + +// UnmarshalJSON decodes a float64 from JSON. +func (x *Float64) UnmarshalJSON(b []byte) error { + var v float64 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + x.Store(v) + return nil +} diff --git a/vendor/go.uber.org/atomic/float64_ext.go b/vendor/go.uber.org/atomic/float64_ext.go new file mode 100644 index 0000000000..48c52b0abf --- /dev/null +++ b/vendor/go.uber.org/atomic/float64_ext.go @@ -0,0 +1,76 @@ +// Copyright (c) 2020-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "math" + "strconv" +) + +//go:generate bin/gen-atomicwrapper -name=Float64 -type=float64 -wrapped=Uint64 -pack=math.Float64bits -unpack=math.Float64frombits -swap -json -imports math -file=float64.go + +// Add atomically adds to the wrapped float64 and returns the new value. +func (f *Float64) Add(delta float64) float64 { + for { + old := f.Load() + new := old + delta + if f.CAS(old, new) { + return new + } + } +} + +// Sub atomically subtracts from the wrapped float64 and returns the new value. +func (f *Float64) Sub(delta float64) float64 { + return f.Add(-delta) +} + +// CAS is an atomic compare-and-swap for float64 values. +// +// Deprecated: Use CompareAndSwap +func (f *Float64) CAS(old, new float64) (swapped bool) { + return f.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap for float64 values. +// +// Note: CompareAndSwap handles NaN incorrectly. NaN != NaN using Go's inbuilt operators +// but CompareAndSwap allows a stored NaN to compare equal to a passed in NaN. +// This avoids typical CompareAndSwap loops from blocking forever, e.g., +// +// for { +// old := atom.Load() +// new = f(old) +// if atom.CompareAndSwap(old, new) { +// break +// } +// } +// +// If CompareAndSwap did not match NaN to match, then the above would loop forever. +func (f *Float64) CompareAndSwap(old, new float64) (swapped bool) { + return f.v.CompareAndSwap(math.Float64bits(old), math.Float64bits(new)) +} + +// String encodes the wrapped value as a string. +func (f *Float64) String() string { + // 'g' is the behavior for floats with %v. + return strconv.FormatFloat(f.Load(), 'g', -1, 64) +} diff --git a/vendor/go.uber.org/atomic/gen.go b/vendor/go.uber.org/atomic/gen.go new file mode 100644 index 0000000000..1e9ef4f879 --- /dev/null +++ b/vendor/go.uber.org/atomic/gen.go @@ -0,0 +1,27 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +//go:generate bin/gen-atomicint -name=Int32 -wrapped=int32 -file=int32.go +//go:generate bin/gen-atomicint -name=Int64 -wrapped=int64 -file=int64.go +//go:generate bin/gen-atomicint -name=Uint32 -wrapped=uint32 -unsigned -file=uint32.go +//go:generate bin/gen-atomicint -name=Uint64 -wrapped=uint64 -unsigned -file=uint64.go +//go:generate bin/gen-atomicint -name=Uintptr -wrapped=uintptr -unsigned -file=uintptr.go diff --git a/vendor/go.uber.org/atomic/int32.go b/vendor/go.uber.org/atomic/int32.go new file mode 100644 index 0000000000..5320eac10f --- /dev/null +++ b/vendor/go.uber.org/atomic/int32.go @@ -0,0 +1,109 @@ +// @generated Code generated by gen-atomicint. + +// Copyright (c) 2020-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" + "strconv" + "sync/atomic" +) + +// Int32 is an atomic wrapper around int32. +type Int32 struct { + _ nocmp // disallow non-atomic comparison + + v int32 +} + +// NewInt32 creates a new Int32. +func NewInt32(val int32) *Int32 { + return &Int32{v: val} +} + +// Load atomically loads the wrapped value. +func (i *Int32) Load() int32 { + return atomic.LoadInt32(&i.v) +} + +// Add atomically adds to the wrapped int32 and returns the new value. +func (i *Int32) Add(delta int32) int32 { + return atomic.AddInt32(&i.v, delta) +} + +// Sub atomically subtracts from the wrapped int32 and returns the new value. +func (i *Int32) Sub(delta int32) int32 { + return atomic.AddInt32(&i.v, -delta) +} + +// Inc atomically increments the wrapped int32 and returns the new value. +func (i *Int32) Inc() int32 { + return i.Add(1) +} + +// Dec atomically decrements the wrapped int32 and returns the new value. +func (i *Int32) Dec() int32 { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +// +// Deprecated: Use CompareAndSwap. +func (i *Int32) CAS(old, new int32) (swapped bool) { + return i.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (i *Int32) CompareAndSwap(old, new int32) (swapped bool) { + return atomic.CompareAndSwapInt32(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Int32) Store(val int32) { + atomic.StoreInt32(&i.v, val) +} + +// Swap atomically swaps the wrapped int32 and returns the old value. +func (i *Int32) Swap(val int32) (old int32) { + return atomic.SwapInt32(&i.v, val) +} + +// MarshalJSON encodes the wrapped int32 into JSON. +func (i *Int32) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Load()) +} + +// UnmarshalJSON decodes JSON into the wrapped int32. +func (i *Int32) UnmarshalJSON(b []byte) error { + var v int32 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + i.Store(v) + return nil +} + +// String encodes the wrapped value as a string. +func (i *Int32) String() string { + v := i.Load() + return strconv.FormatInt(int64(v), 10) +} diff --git a/vendor/go.uber.org/atomic/int64.go b/vendor/go.uber.org/atomic/int64.go new file mode 100644 index 0000000000..460821d009 --- /dev/null +++ b/vendor/go.uber.org/atomic/int64.go @@ -0,0 +1,109 @@ +// @generated Code generated by gen-atomicint. + +// Copyright (c) 2020-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" + "strconv" + "sync/atomic" +) + +// Int64 is an atomic wrapper around int64. +type Int64 struct { + _ nocmp // disallow non-atomic comparison + + v int64 +} + +// NewInt64 creates a new Int64. +func NewInt64(val int64) *Int64 { + return &Int64{v: val} +} + +// Load atomically loads the wrapped value. +func (i *Int64) Load() int64 { + return atomic.LoadInt64(&i.v) +} + +// Add atomically adds to the wrapped int64 and returns the new value. +func (i *Int64) Add(delta int64) int64 { + return atomic.AddInt64(&i.v, delta) +} + +// Sub atomically subtracts from the wrapped int64 and returns the new value. +func (i *Int64) Sub(delta int64) int64 { + return atomic.AddInt64(&i.v, -delta) +} + +// Inc atomically increments the wrapped int64 and returns the new value. +func (i *Int64) Inc() int64 { + return i.Add(1) +} + +// Dec atomically decrements the wrapped int64 and returns the new value. +func (i *Int64) Dec() int64 { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +// +// Deprecated: Use CompareAndSwap. +func (i *Int64) CAS(old, new int64) (swapped bool) { + return i.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (i *Int64) CompareAndSwap(old, new int64) (swapped bool) { + return atomic.CompareAndSwapInt64(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Int64) Store(val int64) { + atomic.StoreInt64(&i.v, val) +} + +// Swap atomically swaps the wrapped int64 and returns the old value. +func (i *Int64) Swap(val int64) (old int64) { + return atomic.SwapInt64(&i.v, val) +} + +// MarshalJSON encodes the wrapped int64 into JSON. +func (i *Int64) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Load()) +} + +// UnmarshalJSON decodes JSON into the wrapped int64. +func (i *Int64) UnmarshalJSON(b []byte) error { + var v int64 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + i.Store(v) + return nil +} + +// String encodes the wrapped value as a string. +func (i *Int64) String() string { + v := i.Load() + return strconv.FormatInt(int64(v), 10) +} diff --git a/vendor/go.uber.org/atomic/internal/gen-atomicint/main.go b/vendor/go.uber.org/atomic/internal/gen-atomicint/main.go new file mode 100644 index 0000000000..719fe9c903 --- /dev/null +++ b/vendor/go.uber.org/atomic/internal/gen-atomicint/main.go @@ -0,0 +1,116 @@ +// Copyright (c) 2020-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// gen-atomicint generates an atomic wrapper around an integer type. +// +// gen-atomicint -name Int32 -wrapped int32 -file out.go +// +// The generated wrapper will use the functions in the sync/atomic package +// named after the generated type. +package main + +import ( + "bytes" + "embed" + "errors" + "flag" + "fmt" + "go/format" + "io" + "log" + "os" + "text/template" + "time" +) + +func main() { + log.SetFlags(0) + if err := run(os.Args[1:]); err != nil { + log.Fatalf("%+v", err) + } +} + +func run(args []string) error { + var opts struct { + Name string + Wrapped string + File string + Unsigned bool + } + + flag := flag.NewFlagSet("gen-atomicint", flag.ContinueOnError) + + flag.StringVar(&opts.Name, "name", "", "name of the generated type (e.g. Int32)") + flag.StringVar(&opts.Wrapped, "wrapped", "", "name of the wrapped type (e.g. int32)") + flag.StringVar(&opts.File, "file", "", "output file path (default: stdout)") + flag.BoolVar(&opts.Unsigned, "unsigned", false, "whether the type is unsigned") + + if err := flag.Parse(args); err != nil { + return err + } + + if len(opts.Name) == 0 || len(opts.Wrapped) == 0 { + return errors.New("flags -name and -wrapped are required") + } + + var w io.Writer = os.Stdout + if file := opts.File; len(file) > 0 { + f, err := os.Create(file) + if err != nil { + return fmt.Errorf("create %q: %v", file, err) + } + defer f.Close() + + w = f + } + + data := struct { + Name string + Wrapped string + Unsigned bool + ToYear int + }{ + Name: opts.Name, + Wrapped: opts.Wrapped, + Unsigned: opts.Unsigned, + ToYear: time.Now().Year(), + } + + var buff bytes.Buffer + if err := _tmpl.ExecuteTemplate(&buff, "wrapper.tmpl", data); err != nil { + return fmt.Errorf("render template: %v", err) + } + + bs, err := format.Source(buff.Bytes()) + if err != nil { + return fmt.Errorf("reformat source: %v", err) + } + + io.WriteString(w, "// @generated Code generated by gen-atomicint.\n\n") + _, err = w.Write(bs) + return err +} + +var ( + //go:embed *.tmpl + _tmplFS embed.FS + + _tmpl = template.Must(template.New("atomicint").ParseFS(_tmplFS, "*.tmpl")) +) diff --git a/vendor/go.uber.org/atomic/internal/gen-atomicint/wrapper.tmpl b/vendor/go.uber.org/atomic/internal/gen-atomicint/wrapper.tmpl new file mode 100644 index 0000000000..51a0e3bf29 --- /dev/null +++ b/vendor/go.uber.org/atomic/internal/gen-atomicint/wrapper.tmpl @@ -0,0 +1,117 @@ +// Copyright (c) 2020-{{.ToYear}} Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" + "strconv" + "sync/atomic" +) + +// {{ .Name }} is an atomic wrapper around {{ .Wrapped }}. +type {{ .Name }} struct { + _ nocmp // disallow non-atomic comparison + + v {{ .Wrapped }} +} + +// New{{ .Name }} creates a new {{ .Name }}. +func New{{ .Name }}(val {{ .Wrapped }}) *{{ .Name }} { + return &{{ .Name }}{v: val} +} + +// Load atomically loads the wrapped value. +func (i *{{ .Name }}) Load() {{ .Wrapped }} { + return atomic.Load{{ .Name }}(&i.v) +} + +// Add atomically adds to the wrapped {{ .Wrapped }} and returns the new value. +func (i *{{ .Name }}) Add(delta {{ .Wrapped }}) {{ .Wrapped }} { + return atomic.Add{{ .Name }}(&i.v, delta) +} + +// Sub atomically subtracts from the wrapped {{ .Wrapped }} and returns the new value. +func (i *{{ .Name }}) Sub(delta {{ .Wrapped }}) {{ .Wrapped }} { + return atomic.Add{{ .Name }}(&i.v, + {{- if .Unsigned -}} + ^(delta - 1) + {{- else -}} + -delta + {{- end -}} + ) +} + +// Inc atomically increments the wrapped {{ .Wrapped }} and returns the new value. +func (i *{{ .Name }}) Inc() {{ .Wrapped }} { + return i.Add(1) +} + +// Dec atomically decrements the wrapped {{ .Wrapped }} and returns the new value. +func (i *{{ .Name }}) Dec() {{ .Wrapped }} { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +// +// Deprecated: Use CompareAndSwap. +func (i *{{ .Name }}) CAS(old, new {{ .Wrapped }}) (swapped bool) { + return i.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (i *{{ .Name }}) CompareAndSwap(old, new {{ .Wrapped }}) (swapped bool) { + return atomic.CompareAndSwap{{ .Name }}(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *{{ .Name }}) Store(val {{ .Wrapped }}) { + atomic.Store{{ .Name }}(&i.v, val) +} + +// Swap atomically swaps the wrapped {{ .Wrapped }} and returns the old value. +func (i *{{ .Name }}) Swap(val {{ .Wrapped }}) (old {{ .Wrapped }}) { + return atomic.Swap{{ .Name }}(&i.v, val) +} + +// MarshalJSON encodes the wrapped {{ .Wrapped }} into JSON. +func (i *{{ .Name }}) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Load()) +} + +// UnmarshalJSON decodes JSON into the wrapped {{ .Wrapped }}. +func (i *{{ .Name }}) UnmarshalJSON(b []byte) error { + var v {{ .Wrapped }} + if err := json.Unmarshal(b, &v); err != nil { + return err + } + i.Store(v) + return nil +} + +// String encodes the wrapped value as a string. +func (i *{{ .Name }}) String() string { + v := i.Load() + {{ if .Unsigned -}} + return strconv.FormatUint(uint64(v), 10) + {{- else -}} + return strconv.FormatInt(int64(v), 10) + {{- end }} +} diff --git a/vendor/go.uber.org/atomic/internal/gen-atomicint/ya.make b/vendor/go.uber.org/atomic/internal/gen-atomicint/ya.make new file mode 100644 index 0000000000..0263a35a79 --- /dev/null +++ b/vendor/go.uber.org/atomic/internal/gen-atomicint/ya.make @@ -0,0 +1,9 @@ +GO_PROGRAM() + +LICENSE(MIT) + +SRCS(main.go) + +GO_EMBED_PATTERN(*.tmpl) + +END() diff --git a/vendor/go.uber.org/atomic/internal/gen-atomicwrapper/main.go b/vendor/go.uber.org/atomic/internal/gen-atomicwrapper/main.go new file mode 100644 index 0000000000..982254d39d --- /dev/null +++ b/vendor/go.uber.org/atomic/internal/gen-atomicwrapper/main.go @@ -0,0 +1,203 @@ +// Copyright (c) 2020-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// gen-atomicwrapper generates wrapper types around other atomic types. +// +// It supports plugging in functions which convert the value inside the atomic +// type to the user-facing value. For example, +// +// Given, atomic.Value and the functions, +// +// func packString(string) interface{} +// func unpackString(interface{}) string +// +// We can run the following command: +// +// gen-atomicwrapper -name String -wrapped Value \ +// -type string -pack fromString -unpack tostring +// +// This wil generate approximately, +// +// type String struct{ v Value } +// +// func (s *String) Load() string { +// return unpackString(v.Load()) +// } +// +// func (s *String) Store(s string) { +// return s.v.Store(packString(s)) +// } +// +// The packing/unpacking logic allows the stored value to be different from +// the user-facing value. +package main + +import ( + "bytes" + "embed" + "errors" + "flag" + "fmt" + "go/format" + "io" + "log" + "os" + "sort" + "strings" + "text/template" + "time" +) + +func main() { + log.SetFlags(0) + if err := run(os.Args[1:]); err != nil { + log.Fatalf("%+v", err) + } +} + +type stringList []string + +func (sl *stringList) String() string { + return strings.Join(*sl, ",") +} + +func (sl *stringList) Set(s string) error { + for _, i := range strings.Split(s, ",") { + *sl = append(*sl, strings.TrimSpace(i)) + } + return nil +} + +func run(args []string) error { + var opts struct { + Name string + Wrapped string + Type string + + Imports stringList + Pack, Unpack string + + CAS bool + CompareAndSwap bool + Swap bool + JSON bool + + File string + ToYear int + } + + opts.ToYear = time.Now().Year() + + flag := flag.NewFlagSet("gen-atomicwrapper", flag.ContinueOnError) + + // Required flags + flag.StringVar(&opts.Name, "name", "", + "name of the generated type (e.g. Duration)") + flag.StringVar(&opts.Wrapped, "wrapped", "", + "name of the wrapped atomic (e.g. Int64)") + flag.StringVar(&opts.Type, "type", "", + "name of the type exposed by the atomic (e.g. time.Duration)") + + // Optional flags + flag.Var(&opts.Imports, "imports", + "comma separated list of imports to add") + flag.StringVar(&opts.Pack, "pack", "", + "function to transform values with before storage") + flag.StringVar(&opts.Unpack, "unpack", "", + "function to reverse packing on loading") + flag.StringVar(&opts.File, "file", "", + "output file path (default: stdout)") + + // Switches for individual methods. Underlying atomics must support + // these. + flag.BoolVar(&opts.CAS, "cas", false, + "generate a deprecated `CAS(old, new) bool` method; requires -pack") + flag.BoolVar(&opts.CompareAndSwap, "compareandswap", false, + "generate a `CompareAndSwap(old, new) bool` method; requires -pack") + flag.BoolVar(&opts.Swap, "swap", false, + "generate a `Swap(new) old` method; requires -pack and -unpack") + flag.BoolVar(&opts.JSON, "json", false, + "generate `MarshalJSON/UnmarshJSON` methods") + + if err := flag.Parse(args); err != nil { + return err + } + + if len(opts.Name) == 0 || + len(opts.Wrapped) == 0 || + len(opts.Type) == 0 || + len(opts.Pack) == 0 || + len(opts.Unpack) == 0 { + return errors.New("flags -name, -wrapped, -pack, -unpack and -type are required") + } + + if opts.CAS { + opts.CompareAndSwap = true + } + + var w io.Writer = os.Stdout + if file := opts.File; len(file) > 0 { + f, err := os.Create(file) + if err != nil { + return fmt.Errorf("create %q: %v", file, err) + } + defer f.Close() + + w = f + } + + // Import encoding/json if needed. + if opts.JSON { + found := false + for _, imp := range opts.Imports { + if imp == "encoding/json" { + found = true + break + } + } + + if !found { + opts.Imports = append(opts.Imports, "encoding/json") + } + } + + sort.Strings([]string(opts.Imports)) + + var buff bytes.Buffer + if err := _tmpl.ExecuteTemplate(&buff, "wrapper.tmpl", opts); err != nil { + return fmt.Errorf("render template: %v", err) + } + + bs, err := format.Source(buff.Bytes()) + if err != nil { + return fmt.Errorf("reformat source: %v", err) + } + + io.WriteString(w, "// @generated Code generated by gen-atomicwrapper.\n\n") + _, err = w.Write(bs) + return err +} + +var ( + //go:embed *.tmpl + _tmplFS embed.FS + + _tmpl = template.Must(template.New("atomicwrapper").ParseFS(_tmplFS, "*.tmpl")) +) diff --git a/vendor/go.uber.org/atomic/internal/gen-atomicwrapper/wrapper.tmpl b/vendor/go.uber.org/atomic/internal/gen-atomicwrapper/wrapper.tmpl new file mode 100644 index 0000000000..47e0253cc6 --- /dev/null +++ b/vendor/go.uber.org/atomic/internal/gen-atomicwrapper/wrapper.tmpl @@ -0,0 +1,120 @@ +// Copyright (c) 2020-{{.ToYear}} Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +{{ with .Imports }} +import ( + {{ range . -}} + {{ printf "%q" . }} + {{ end }} +) +{{ end }} + +// {{ .Name }} is an atomic type-safe wrapper for {{ .Type }} values. +type {{ .Name }} struct{ + _ nocmp // disallow non-atomic comparison + + v {{ .Wrapped }} +} + +var _zero{{ .Name }} {{ .Type }} + + +// New{{ .Name }} creates a new {{ .Name }}. +func New{{ .Name }}(val {{ .Type }}) *{{ .Name }} { + x := &{{ .Name }}{} + if val != _zero{{ .Name }} { + x.Store(val) + } + return x +} + +// Load atomically loads the wrapped {{ .Type }}. +func (x *{{ .Name }}) Load() {{ .Type }} { + {{ if .Unpack -}} + return {{ .Unpack }}(x.v.Load()) + {{- else -}} + if v := x.v.Load(); v != nil { + return v.({{ .Type }}) + } + return _zero{{ .Name }} + {{- end }} +} + +// Store atomically stores the passed {{ .Type }}. +func (x *{{ .Name }}) Store(val {{ .Type }}) { + x.v.Store({{ .Pack }}(val)) +} + +{{ if .CAS -}} + // CAS is an atomic compare-and-swap for {{ .Type }} values. + // + // Deprecated: Use CompareAndSwap. + func (x *{{ .Name }}) CAS(old, new {{ .Type }}) (swapped bool) { + return x.CompareAndSwap(old, new) + } +{{- end }} + +{{ if .CompareAndSwap -}} + // CompareAndSwap is an atomic compare-and-swap for {{ .Type }} values. + func (x *{{ .Name }}) CompareAndSwap(old, new {{ .Type }}) (swapped bool) { + {{ if eq .Wrapped "Value" -}} + if x.v.CompareAndSwap({{ .Pack }}(old), {{ .Pack }}(new)) { + return true + } + + if old == _zero{{ .Name }} { + // If the old value is the empty value, then it's possible the + // underlying Value hasn't been set and is nil, so retry with nil. + return x.v.CompareAndSwap(nil, {{ .Pack }}(new)) + } + + return false + {{- else -}} + return x.v.CompareAndSwap({{ .Pack }}(old), {{ .Pack }}(new)) + {{- end }} + } +{{- end }} + +{{ if .Swap -}} + // Swap atomically stores the given {{ .Type }} and returns the old + // value. + func (x *{{ .Name }}) Swap(val {{ .Type }}) (old {{ .Type }}) { + return {{ .Unpack }}(x.v.Swap({{ .Pack }}(val))) + } +{{- end }} + +{{ if .JSON -}} + // MarshalJSON encodes the wrapped {{ .Type }} into JSON. + func (x *{{ .Name }}) MarshalJSON() ([]byte, error) { + return json.Marshal(x.Load()) + } + + // UnmarshalJSON decodes a {{ .Type }} from JSON. + func (x *{{ .Name }}) UnmarshalJSON(b []byte) error { + var v {{ .Type }} + if err := json.Unmarshal(b, &v); err != nil { + return err + } + x.Store(v) + return nil + } +{{- end }} diff --git a/vendor/go.uber.org/atomic/internal/gen-atomicwrapper/ya.make b/vendor/go.uber.org/atomic/internal/gen-atomicwrapper/ya.make new file mode 100644 index 0000000000..0263a35a79 --- /dev/null +++ b/vendor/go.uber.org/atomic/internal/gen-atomicwrapper/ya.make @@ -0,0 +1,9 @@ +GO_PROGRAM() + +LICENSE(MIT) + +SRCS(main.go) + +GO_EMBED_PATTERN(*.tmpl) + +END() diff --git a/vendor/go.uber.org/atomic/internal/ya.make b/vendor/go.uber.org/atomic/internal/ya.make new file mode 100644 index 0000000000..d018e0302e --- /dev/null +++ b/vendor/go.uber.org/atomic/internal/ya.make @@ -0,0 +1,4 @@ +RECURSE( + gen-atomicint + gen-atomicwrapper +) diff --git a/vendor/go.uber.org/atomic/nocmp.go b/vendor/go.uber.org/atomic/nocmp.go new file mode 100644 index 0000000000..54b74174ab --- /dev/null +++ b/vendor/go.uber.org/atomic/nocmp.go @@ -0,0 +1,35 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +// nocmp is an uncomparable struct. Embed this inside another struct to make +// it uncomparable. +// +// type Foo struct { +// nocmp +// // ... +// } +// +// This DOES NOT: +// +// - Disallow shallow copies of structs +// - Disallow comparison of pointers to uncomparable structs +type nocmp [0]func() diff --git a/vendor/go.uber.org/atomic/pointer_go118.go b/vendor/go.uber.org/atomic/pointer_go118.go new file mode 100644 index 0000000000..1fb6c03b26 --- /dev/null +++ b/vendor/go.uber.org/atomic/pointer_go118.go @@ -0,0 +1,31 @@ +// Copyright (c) 2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//go:build go1.18 +// +build go1.18 + +package atomic + +import "fmt" + +// String returns a human readable representation of a Pointer's underlying value. +func (p *Pointer[T]) String() string { + return fmt.Sprint(p.Load()) +} diff --git a/vendor/go.uber.org/atomic/pointer_go119.go b/vendor/go.uber.org/atomic/pointer_go119.go new file mode 100644 index 0000000000..6726f17ad6 --- /dev/null +++ b/vendor/go.uber.org/atomic/pointer_go119.go @@ -0,0 +1,61 @@ +// Copyright (c) 2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//go:build go1.19 +// +build go1.19 + +package atomic + +import "sync/atomic" + +// Pointer is an atomic pointer of type *T. +type Pointer[T any] struct { + _ nocmp // disallow non-atomic comparison + p atomic.Pointer[T] +} + +// NewPointer creates a new Pointer. +func NewPointer[T any](v *T) *Pointer[T] { + var p Pointer[T] + if v != nil { + p.p.Store(v) + } + return &p +} + +// Load atomically loads the wrapped value. +func (p *Pointer[T]) Load() *T { + return p.p.Load() +} + +// Store atomically stores the passed value. +func (p *Pointer[T]) Store(val *T) { + p.p.Store(val) +} + +// Swap atomically swaps the wrapped pointer and returns the old value. +func (p *Pointer[T]) Swap(val *T) (old *T) { + return p.p.Swap(val) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (p *Pointer[T]) CompareAndSwap(old, new *T) (swapped bool) { + return p.p.CompareAndSwap(old, new) +} diff --git a/vendor/go.uber.org/atomic/string.go b/vendor/go.uber.org/atomic/string.go new file mode 100644 index 0000000000..061466c5bd --- /dev/null +++ b/vendor/go.uber.org/atomic/string.go @@ -0,0 +1,72 @@ +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +// String is an atomic type-safe wrapper for string values. +type String struct { + _ nocmp // disallow non-atomic comparison + + v Value +} + +var _zeroString string + +// NewString creates a new String. +func NewString(val string) *String { + x := &String{} + if val != _zeroString { + x.Store(val) + } + return x +} + +// Load atomically loads the wrapped string. +func (x *String) Load() string { + return unpackString(x.v.Load()) +} + +// Store atomically stores the passed string. +func (x *String) Store(val string) { + x.v.Store(packString(val)) +} + +// CompareAndSwap is an atomic compare-and-swap for string values. +func (x *String) CompareAndSwap(old, new string) (swapped bool) { + if x.v.CompareAndSwap(packString(old), packString(new)) { + return true + } + + if old == _zeroString { + // If the old value is the empty value, then it's possible the + // underlying Value hasn't been set and is nil, so retry with nil. + return x.v.CompareAndSwap(nil, packString(new)) + } + + return false +} + +// Swap atomically stores the given string and returns the old +// value. +func (x *String) Swap(val string) (old string) { + return unpackString(x.v.Swap(packString(val))) +} diff --git a/vendor/go.uber.org/atomic/string_ext.go b/vendor/go.uber.org/atomic/string_ext.go new file mode 100644 index 0000000000..019109c86b --- /dev/null +++ b/vendor/go.uber.org/atomic/string_ext.go @@ -0,0 +1,54 @@ +// Copyright (c) 2020-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +//go:generate bin/gen-atomicwrapper -name=String -type=string -wrapped Value -pack packString -unpack unpackString -compareandswap -swap -file=string.go + +func packString(s string) interface{} { + return s +} + +func unpackString(v interface{}) string { + if s, ok := v.(string); ok { + return s + } + return "" +} + +// String returns the wrapped value. +func (s *String) String() string { + return s.Load() +} + +// MarshalText encodes the wrapped string into a textual form. +// +// This makes it encodable as JSON, YAML, XML, and more. +func (s *String) MarshalText() ([]byte, error) { + return []byte(s.Load()), nil +} + +// UnmarshalText decodes text and replaces the wrapped string with it. +// +// This makes it decodable from JSON, YAML, XML, and more. +func (s *String) UnmarshalText(b []byte) error { + s.Store(string(b)) + return nil +} diff --git a/vendor/go.uber.org/atomic/time.go b/vendor/go.uber.org/atomic/time.go new file mode 100644 index 0000000000..cc2a230c00 --- /dev/null +++ b/vendor/go.uber.org/atomic/time.go @@ -0,0 +1,55 @@ +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "time" +) + +// Time is an atomic type-safe wrapper for time.Time values. +type Time struct { + _ nocmp // disallow non-atomic comparison + + v Value +} + +var _zeroTime time.Time + +// NewTime creates a new Time. +func NewTime(val time.Time) *Time { + x := &Time{} + if val != _zeroTime { + x.Store(val) + } + return x +} + +// Load atomically loads the wrapped time.Time. +func (x *Time) Load() time.Time { + return unpackTime(x.v.Load()) +} + +// Store atomically stores the passed time.Time. +func (x *Time) Store(val time.Time) { + x.v.Store(packTime(val)) +} diff --git a/vendor/go.uber.org/atomic/time_ext.go b/vendor/go.uber.org/atomic/time_ext.go new file mode 100644 index 0000000000..1e3dc978aa --- /dev/null +++ b/vendor/go.uber.org/atomic/time_ext.go @@ -0,0 +1,36 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import "time" + +//go:generate bin/gen-atomicwrapper -name=Time -type=time.Time -wrapped=Value -pack=packTime -unpack=unpackTime -imports time -file=time.go + +func packTime(t time.Time) interface{} { + return t +} + +func unpackTime(v interface{}) time.Time { + if t, ok := v.(time.Time); ok { + return t + } + return time.Time{} +} diff --git a/vendor/go.uber.org/atomic/uint32.go b/vendor/go.uber.org/atomic/uint32.go new file mode 100644 index 0000000000..4adc294ac2 --- /dev/null +++ b/vendor/go.uber.org/atomic/uint32.go @@ -0,0 +1,109 @@ +// @generated Code generated by gen-atomicint. + +// Copyright (c) 2020-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" + "strconv" + "sync/atomic" +) + +// Uint32 is an atomic wrapper around uint32. +type Uint32 struct { + _ nocmp // disallow non-atomic comparison + + v uint32 +} + +// NewUint32 creates a new Uint32. +func NewUint32(val uint32) *Uint32 { + return &Uint32{v: val} +} + +// Load atomically loads the wrapped value. +func (i *Uint32) Load() uint32 { + return atomic.LoadUint32(&i.v) +} + +// Add atomically adds to the wrapped uint32 and returns the new value. +func (i *Uint32) Add(delta uint32) uint32 { + return atomic.AddUint32(&i.v, delta) +} + +// Sub atomically subtracts from the wrapped uint32 and returns the new value. +func (i *Uint32) Sub(delta uint32) uint32 { + return atomic.AddUint32(&i.v, ^(delta - 1)) +} + +// Inc atomically increments the wrapped uint32 and returns the new value. +func (i *Uint32) Inc() uint32 { + return i.Add(1) +} + +// Dec atomically decrements the wrapped uint32 and returns the new value. +func (i *Uint32) Dec() uint32 { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +// +// Deprecated: Use CompareAndSwap. +func (i *Uint32) CAS(old, new uint32) (swapped bool) { + return i.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (i *Uint32) CompareAndSwap(old, new uint32) (swapped bool) { + return atomic.CompareAndSwapUint32(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Uint32) Store(val uint32) { + atomic.StoreUint32(&i.v, val) +} + +// Swap atomically swaps the wrapped uint32 and returns the old value. +func (i *Uint32) Swap(val uint32) (old uint32) { + return atomic.SwapUint32(&i.v, val) +} + +// MarshalJSON encodes the wrapped uint32 into JSON. +func (i *Uint32) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Load()) +} + +// UnmarshalJSON decodes JSON into the wrapped uint32. +func (i *Uint32) UnmarshalJSON(b []byte) error { + var v uint32 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + i.Store(v) + return nil +} + +// String encodes the wrapped value as a string. +func (i *Uint32) String() string { + v := i.Load() + return strconv.FormatUint(uint64(v), 10) +} diff --git a/vendor/go.uber.org/atomic/uint64.go b/vendor/go.uber.org/atomic/uint64.go new file mode 100644 index 0000000000..0e2eddb303 --- /dev/null +++ b/vendor/go.uber.org/atomic/uint64.go @@ -0,0 +1,109 @@ +// @generated Code generated by gen-atomicint. + +// Copyright (c) 2020-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" + "strconv" + "sync/atomic" +) + +// Uint64 is an atomic wrapper around uint64. +type Uint64 struct { + _ nocmp // disallow non-atomic comparison + + v uint64 +} + +// NewUint64 creates a new Uint64. +func NewUint64(val uint64) *Uint64 { + return &Uint64{v: val} +} + +// Load atomically loads the wrapped value. +func (i *Uint64) Load() uint64 { + return atomic.LoadUint64(&i.v) +} + +// Add atomically adds to the wrapped uint64 and returns the new value. +func (i *Uint64) Add(delta uint64) uint64 { + return atomic.AddUint64(&i.v, delta) +} + +// Sub atomically subtracts from the wrapped uint64 and returns the new value. +func (i *Uint64) Sub(delta uint64) uint64 { + return atomic.AddUint64(&i.v, ^(delta - 1)) +} + +// Inc atomically increments the wrapped uint64 and returns the new value. +func (i *Uint64) Inc() uint64 { + return i.Add(1) +} + +// Dec atomically decrements the wrapped uint64 and returns the new value. +func (i *Uint64) Dec() uint64 { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +// +// Deprecated: Use CompareAndSwap. +func (i *Uint64) CAS(old, new uint64) (swapped bool) { + return i.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (i *Uint64) CompareAndSwap(old, new uint64) (swapped bool) { + return atomic.CompareAndSwapUint64(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Uint64) Store(val uint64) { + atomic.StoreUint64(&i.v, val) +} + +// Swap atomically swaps the wrapped uint64 and returns the old value. +func (i *Uint64) Swap(val uint64) (old uint64) { + return atomic.SwapUint64(&i.v, val) +} + +// MarshalJSON encodes the wrapped uint64 into JSON. +func (i *Uint64) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Load()) +} + +// UnmarshalJSON decodes JSON into the wrapped uint64. +func (i *Uint64) UnmarshalJSON(b []byte) error { + var v uint64 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + i.Store(v) + return nil +} + +// String encodes the wrapped value as a string. +func (i *Uint64) String() string { + v := i.Load() + return strconv.FormatUint(uint64(v), 10) +} diff --git a/vendor/go.uber.org/atomic/uintptr.go b/vendor/go.uber.org/atomic/uintptr.go new file mode 100644 index 0000000000..7d5b000d61 --- /dev/null +++ b/vendor/go.uber.org/atomic/uintptr.go @@ -0,0 +1,109 @@ +// @generated Code generated by gen-atomicint. + +// Copyright (c) 2020-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" + "strconv" + "sync/atomic" +) + +// Uintptr is an atomic wrapper around uintptr. +type Uintptr struct { + _ nocmp // disallow non-atomic comparison + + v uintptr +} + +// NewUintptr creates a new Uintptr. +func NewUintptr(val uintptr) *Uintptr { + return &Uintptr{v: val} +} + +// Load atomically loads the wrapped value. +func (i *Uintptr) Load() uintptr { + return atomic.LoadUintptr(&i.v) +} + +// Add atomically adds to the wrapped uintptr and returns the new value. +func (i *Uintptr) Add(delta uintptr) uintptr { + return atomic.AddUintptr(&i.v, delta) +} + +// Sub atomically subtracts from the wrapped uintptr and returns the new value. +func (i *Uintptr) Sub(delta uintptr) uintptr { + return atomic.AddUintptr(&i.v, ^(delta - 1)) +} + +// Inc atomically increments the wrapped uintptr and returns the new value. +func (i *Uintptr) Inc() uintptr { + return i.Add(1) +} + +// Dec atomically decrements the wrapped uintptr and returns the new value. +func (i *Uintptr) Dec() uintptr { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +// +// Deprecated: Use CompareAndSwap. +func (i *Uintptr) CAS(old, new uintptr) (swapped bool) { + return i.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (i *Uintptr) CompareAndSwap(old, new uintptr) (swapped bool) { + return atomic.CompareAndSwapUintptr(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Uintptr) Store(val uintptr) { + atomic.StoreUintptr(&i.v, val) +} + +// Swap atomically swaps the wrapped uintptr and returns the old value. +func (i *Uintptr) Swap(val uintptr) (old uintptr) { + return atomic.SwapUintptr(&i.v, val) +} + +// MarshalJSON encodes the wrapped uintptr into JSON. +func (i *Uintptr) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Load()) +} + +// UnmarshalJSON decodes JSON into the wrapped uintptr. +func (i *Uintptr) UnmarshalJSON(b []byte) error { + var v uintptr + if err := json.Unmarshal(b, &v); err != nil { + return err + } + i.Store(v) + return nil +} + +// String encodes the wrapped value as a string. +func (i *Uintptr) String() string { + v := i.Load() + return strconv.FormatUint(uint64(v), 10) +} diff --git a/vendor/go.uber.org/atomic/unsafe_pointer.go b/vendor/go.uber.org/atomic/unsafe_pointer.go new file mode 100644 index 0000000000..34868baf6a --- /dev/null +++ b/vendor/go.uber.org/atomic/unsafe_pointer.go @@ -0,0 +1,65 @@ +// Copyright (c) 2021-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "sync/atomic" + "unsafe" +) + +// UnsafePointer is an atomic wrapper around unsafe.Pointer. +type UnsafePointer struct { + _ nocmp // disallow non-atomic comparison + + v unsafe.Pointer +} + +// NewUnsafePointer creates a new UnsafePointer. +func NewUnsafePointer(val unsafe.Pointer) *UnsafePointer { + return &UnsafePointer{v: val} +} + +// Load atomically loads the wrapped value. +func (p *UnsafePointer) Load() unsafe.Pointer { + return atomic.LoadPointer(&p.v) +} + +// Store atomically stores the passed value. +func (p *UnsafePointer) Store(val unsafe.Pointer) { + atomic.StorePointer(&p.v, val) +} + +// Swap atomically swaps the wrapped unsafe.Pointer and returns the old value. +func (p *UnsafePointer) Swap(val unsafe.Pointer) (old unsafe.Pointer) { + return atomic.SwapPointer(&p.v, val) +} + +// CAS is an atomic compare-and-swap. +// +// Deprecated: Use CompareAndSwap +func (p *UnsafePointer) CAS(old, new unsafe.Pointer) (swapped bool) { + return p.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (p *UnsafePointer) CompareAndSwap(old, new unsafe.Pointer) (swapped bool) { + return atomic.CompareAndSwapPointer(&p.v, old, new) +} diff --git a/vendor/go.uber.org/atomic/value.go b/vendor/go.uber.org/atomic/value.go new file mode 100644 index 0000000000..52caedb9a5 --- /dev/null +++ b/vendor/go.uber.org/atomic/value.go @@ -0,0 +1,31 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import "sync/atomic" + +// Value shadows the type of the same name from sync/atomic +// https://godoc.org/sync/atomic#Value +type Value struct { + _ nocmp // disallow non-atomic comparison + + atomic.Value +} diff --git a/vendor/go.uber.org/atomic/ya.make b/vendor/go.uber.org/atomic/ya.make new file mode 100644 index 0000000000..7eb64b4e0e --- /dev/null +++ b/vendor/go.uber.org/atomic/ya.make @@ -0,0 +1,63 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + bool.go + bool_ext.go + doc.go + duration.go + duration_ext.go + error.go + error_ext.go + float32.go + float32_ext.go + float64.go + float64_ext.go + gen.go + int32.go + int64.go + nocmp.go + pointer_go118.go + pointer_go119.go + string.go + string_ext.go + time.go + time_ext.go + uint32.go + uint64.go + uintptr.go + unsafe_pointer.go + value.go +) + +GO_TEST_SRCS( + assert_test.go + bool_test.go + duration_test.go + error_test.go + float32_test.go + float64_test.go + int32_test.go + int64_test.go + nocmp_test.go + pointer_test.go + stress_test.go + string_test.go + time_test.go + uint32_test.go + uint64_test.go + uintptr_test.go + unsafe_pointer_test.go + value_test.go +) + +GO_XTEST_SRCS(example_test.go) + +END() + +RECURSE(internal) + +IF (NOT OPENSOURCE) + RECURSE(gotest) +ENDIF() diff --git a/vendor/go.uber.org/goleak/doc.go b/vendor/go.uber.org/goleak/doc.go new file mode 100644 index 0000000000..3832f8dbc5 --- /dev/null +++ b/vendor/go.uber.org/goleak/doc.go @@ -0,0 +1,22 @@ +// Copyright (c) 2018 Uber Technologies, Inc. + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package goleak is a Goroutine leak detector. +package goleak // import "go.uber.org/goleak" diff --git a/vendor/go.uber.org/goleak/internal/stack/doc.go b/vendor/go.uber.org/goleak/internal/stack/doc.go new file mode 100644 index 0000000000..9179a56549 --- /dev/null +++ b/vendor/go.uber.org/goleak/internal/stack/doc.go @@ -0,0 +1,22 @@ +// Copyright (c) 2017-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package stack is used for parsing stacks from `runtime.Stack`. +package stack diff --git a/vendor/go.uber.org/goleak/internal/stack/stacks.go b/vendor/go.uber.org/goleak/internal/stack/stacks.go new file mode 100644 index 0000000000..94f82e4c0d --- /dev/null +++ b/vendor/go.uber.org/goleak/internal/stack/stacks.go @@ -0,0 +1,155 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package stack + +import ( + "bufio" + "bytes" + "fmt" + "io" + "runtime" + "strconv" + "strings" +) + +const _defaultBufferSize = 64 * 1024 // 64 KiB + +// Stack represents a single Goroutine's stack. +type Stack struct { + id int + state string + firstFunction string + fullStack *bytes.Buffer +} + +// ID returns the goroutine ID. +func (s Stack) ID() int { + return s.id +} + +// State returns the Goroutine's state. +func (s Stack) State() string { + return s.state +} + +// Full returns the full stack trace for this goroutine. +func (s Stack) Full() string { + return s.fullStack.String() +} + +// FirstFunction returns the name of the first function on the stack. +func (s Stack) FirstFunction() string { + return s.firstFunction +} + +func (s Stack) String() string { + return fmt.Sprintf( + "Goroutine %v in state %v, with %v on top of the stack:\n%s", + s.id, s.state, s.firstFunction, s.Full()) +} + +func getStacks(all bool) []Stack { + var stacks []Stack + + var curStack *Stack + stackReader := bufio.NewReader(bytes.NewReader(getStackBuffer(all))) + for { + line, err := stackReader.ReadString('\n') + if err == io.EOF { + break + } + if err != nil { + // We're reading using bytes.NewReader which should never fail. + panic("bufio.NewReader failed on a fixed string") + } + + // If we see the goroutine header, start a new stack. + isFirstLine := false + if strings.HasPrefix(line, "goroutine ") { + // flush any previous stack + if curStack != nil { + stacks = append(stacks, *curStack) + } + id, goState := parseGoStackHeader(line) + curStack = &Stack{ + id: id, + state: goState, + fullStack: &bytes.Buffer{}, + } + isFirstLine = true + } + curStack.fullStack.WriteString(line) + if !isFirstLine && curStack.firstFunction == "" { + curStack.firstFunction = parseFirstFunc(line) + } + } + + if curStack != nil { + stacks = append(stacks, *curStack) + } + return stacks +} + +// All returns the stacks for all running goroutines. +func All() []Stack { + return getStacks(true) +} + +// Current returns the stack for the current goroutine. +func Current() Stack { + return getStacks(false)[0] +} + +func getStackBuffer(all bool) []byte { + for i := _defaultBufferSize; ; i *= 2 { + buf := make([]byte, i) + if n := runtime.Stack(buf, all); n < i { + return buf[:n] + } + } +} + +func parseFirstFunc(line string) string { + line = strings.TrimSpace(line) + if idx := strings.LastIndex(line, "("); idx > 0 { + return line[:idx] + } + panic(fmt.Sprintf("function calls missing parents: %q", line)) +} + +// parseGoStackHeader parses a stack header that looks like: +// goroutine 643 [runnable]:\n +// And returns the goroutine ID, and the state. +func parseGoStackHeader(line string) (goroutineID int, state string) { + line = strings.TrimSuffix(line, ":\n") + parts := strings.SplitN(line, " ", 3) + if len(parts) != 3 { + panic(fmt.Sprintf("unexpected stack header format: %q", line)) + } + + id, err := strconv.Atoi(parts[1]) + if err != nil { + panic(fmt.Sprintf("failed to parse goroutine ID: %v in line %q", parts[1], line)) + } + + state = strings.TrimSuffix(strings.TrimPrefix(parts[2], "["), "]") + return id, state +} diff --git a/vendor/go.uber.org/goleak/internal/stack/ya.make b/vendor/go.uber.org/goleak/internal/stack/ya.make new file mode 100644 index 0000000000..231f1661d1 --- /dev/null +++ b/vendor/go.uber.org/goleak/internal/stack/ya.make @@ -0,0 +1,14 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + doc.go + stacks.go +) + +GO_TEST_SRCS(stacks_test.go) + +END() + +RECURSE(gotest) diff --git a/vendor/go.uber.org/goleak/leaks.go b/vendor/go.uber.org/goleak/leaks.go new file mode 100644 index 0000000000..ee122b7464 --- /dev/null +++ b/vendor/go.uber.org/goleak/leaks.go @@ -0,0 +1,102 @@ +// Copyright (c) 2017 Uber Technologies, Inc. + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package goleak + +import ( + "errors" + "fmt" + + "go.uber.org/goleak/internal/stack" +) + +// TestingT is the minimal subset of testing.TB that we use. +type TestingT interface { + Error(...interface{}) +} + +// filterStacks will filter any stacks excluded by the given opts. +// filterStacks modifies the passed in stacks slice. +func filterStacks(stacks []stack.Stack, skipID int, opts *opts) []stack.Stack { + filtered := stacks[:0] + for _, stack := range stacks { + // Always skip the running goroutine. + if stack.ID() == skipID { + continue + } + // Run any default or user-specified filters. + if opts.filter(stack) { + continue + } + filtered = append(filtered, stack) + } + return filtered +} + +// Find looks for extra goroutines, and returns a descriptive error if +// any are found. +func Find(options ...Option) error { + cur := stack.Current().ID() + + opts := buildOpts(options...) + if opts.cleanup != nil { + return errors.New("Cleanup can only be passed to VerifyNone or VerifyTestMain") + } + var stacks []stack.Stack + retry := true + for i := 0; retry; i++ { + stacks = filterStacks(stack.All(), cur, opts) + + if len(stacks) == 0 { + return nil + } + retry = opts.retry(i) + } + + return fmt.Errorf("found unexpected goroutines:\n%s", stacks) +} + +type testHelper interface { + Helper() +} + +// VerifyNone marks the given TestingT as failed if any extra goroutines are +// found by Find. This is a helper method to make it easier to integrate in +// tests by doing: +// +// defer VerifyNone(t) +func VerifyNone(t TestingT, options ...Option) { + opts := buildOpts(options...) + var cleanup func(int) + cleanup, opts.cleanup = opts.cleanup, nil + + if h, ok := t.(testHelper); ok { + // Mark this function as a test helper, if available. + h.Helper() + } + + if err := Find(opts); err != nil { + t.Error(err) + } + + if cleanup != nil { + cleanup(0) + } +} diff --git a/vendor/go.uber.org/goleak/options.go b/vendor/go.uber.org/goleak/options.go new file mode 100644 index 0000000000..d2d473b71e --- /dev/null +++ b/vendor/go.uber.org/goleak/options.go @@ -0,0 +1,178 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package goleak + +import ( + "strings" + "time" + + "go.uber.org/goleak/internal/stack" +) + +// Option lets users specify custom verifications. +type Option interface { + apply(*opts) +} + +// We retry up to 20 times if we can't find the goroutine that +// we are looking for. In between each attempt, we will sleep for +// a short while to let any running goroutines complete. +const _defaultRetries = 20 + +type opts struct { + filters []func(stack.Stack) bool + maxRetries int + maxSleep time.Duration + cleanup func(int) +} + +// implement apply so that opts struct itself can be used as +// an Option. +func (o *opts) apply(opts *opts) { + opts.filters = o.filters + opts.maxRetries = o.maxRetries + opts.maxSleep = o.maxSleep + opts.cleanup = o.cleanup +} + +// optionFunc lets us easily write options without a custom type. +type optionFunc func(*opts) + +func (f optionFunc) apply(opts *opts) { f(opts) } + +// IgnoreTopFunction ignores any goroutines where the specified function +// is at the top of the stack. The function name should be fully qualified, +// e.g., go.uber.org/goleak.IgnoreTopFunction +func IgnoreTopFunction(f string) Option { + return addFilter(func(s stack.Stack) bool { + return s.FirstFunction() == f + }) +} + +// Cleanup sets up a cleanup function that will be executed at the +// end of the leak check. +// When passed to [VerifyTestMain], the exit code passed to cleanupFunc +// will be set to the exit code of TestMain. +// When passed to [VerifyNone], the exit code will be set to 0. +// This cannot be passed to [Find]. +func Cleanup(cleanupFunc func(exitCode int)) Option { + return optionFunc(func(opts *opts) { + opts.cleanup = cleanupFunc + }) +} + +// IgnoreCurrent records all current goroutines when the option is created, and ignores +// them in any future Find/Verify calls. +func IgnoreCurrent() Option { + excludeIDSet := map[int]bool{} + for _, s := range stack.All() { + excludeIDSet[s.ID()] = true + } + return addFilter(func(s stack.Stack) bool { + return excludeIDSet[s.ID()] + }) +} + +func maxSleep(d time.Duration) Option { + return optionFunc(func(opts *opts) { + opts.maxSleep = d + }) +} + +func addFilter(f func(stack.Stack) bool) Option { + return optionFunc(func(opts *opts) { + opts.filters = append(opts.filters, f) + }) +} + +func buildOpts(options ...Option) *opts { + opts := &opts{ + maxRetries: _defaultRetries, + maxSleep: 100 * time.Millisecond, + } + opts.filters = append(opts.filters, + isTestStack, + isSyscallStack, + isStdLibStack, + isTraceStack, + ) + for _, option := range options { + option.apply(opts) + } + return opts +} + +func (o *opts) filter(s stack.Stack) bool { + for _, filter := range o.filters { + if filter(s) { + return true + } + } + return false +} + +func (o *opts) retry(i int) bool { + if i >= o.maxRetries { + return false + } + + d := time.Duration(int(time.Microsecond) << uint(i)) + if d > o.maxSleep { + d = o.maxSleep + } + time.Sleep(d) + return true +} + +// isTestStack is a default filter installed to automatically skip goroutines +// that the testing package runs while the user's tests are running. +func isTestStack(s stack.Stack) bool { + // Until go1.7, the main goroutine ran RunTests, which started + // the test in a separate goroutine and waited for that test goroutine + // to end by waiting on a channel. + // Since go1.7, a separate goroutine is started to wait for signals. + // T.Parallel is for parallel tests, which are blocked until all serial + // tests have run with T.Parallel at the top of the stack. + switch s.FirstFunction() { + case "testing.RunTests", "testing.(*T).Run", "testing.(*T).Parallel": + // In pre1.7 and post-1.7, background goroutines started by the testing + // package are blocked waiting on a channel. + return strings.HasPrefix(s.State(), "chan receive") + } + return false +} + +func isSyscallStack(s stack.Stack) bool { + // Typically runs in the background when code uses CGo: + // https://github.com/golang/go/issues/16714 + return s.FirstFunction() == "runtime.goexit" && strings.HasPrefix(s.State(), "syscall") +} + +func isStdLibStack(s stack.Stack) bool { + // Importing os/signal starts a background goroutine. + // The name of the function at the top has changed between versions. + if f := s.FirstFunction(); f == "os/signal.signal_recv" || f == "os/signal.loop" { + return true + } + + // Using signal.Notify will start a runtime goroutine. + return strings.Contains(s.Full(), "runtime.ensureSigM") +} diff --git a/vendor/go.uber.org/goleak/testmain.go b/vendor/go.uber.org/goleak/testmain.go new file mode 100644 index 0000000000..7b1a50b7af --- /dev/null +++ b/vendor/go.uber.org/goleak/testmain.go @@ -0,0 +1,69 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package goleak + +import ( + "fmt" + "io" + "os" +) + +// Variables for stubbing in unit tests. +var ( + _osExit = os.Exit + _osStderr io.Writer = os.Stderr +) + +// TestingM is the minimal subset of testing.M that we use. +type TestingM interface { + Run() int +} + +// VerifyTestMain can be used in a TestMain function for package tests to +// verify that there were no goroutine leaks. +// To use it, your TestMain function should look like: +// +// func TestMain(m *testing.M) { +// goleak.VerifyTestMain(m) +// } +// +// See https://golang.org/pkg/testing/#hdr-Main for more details. +// +// This will run all tests as per normal, and if they were successful, look +// for any goroutine leaks and fail the tests if any leaks were found. +func VerifyTestMain(m TestingM, options ...Option) { + exitCode := m.Run() + opts := buildOpts(options...) + + var cleanup func(int) + cleanup, opts.cleanup = opts.cleanup, nil + if cleanup == nil { + cleanup = _osExit + } + defer func() { cleanup(exitCode) }() + + if exitCode == 0 { + if err := Find(opts); err != nil { + fmt.Fprintf(_osStderr, "goleak: Errors on successful test run: %v\n", err) + exitCode = 1 + } + } +} diff --git a/vendor/go.uber.org/goleak/tracestack_new.go b/vendor/go.uber.org/goleak/tracestack_new.go new file mode 100644 index 0000000000..d12ffd8404 --- /dev/null +++ b/vendor/go.uber.org/goleak/tracestack_new.go @@ -0,0 +1,34 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//go:build go1.16 +// +build go1.16 + +package goleak + +import ( + "strings" + + "go.uber.org/goleak/internal/stack" +) + +func isTraceStack(s stack.Stack) bool { + return strings.Contains(s.Full(), "runtime.ReadTrace") +} diff --git a/vendor/go.uber.org/goleak/ya.make b/vendor/go.uber.org/goleak/ya.make new file mode 100644 index 0000000000..400e32d0d7 --- /dev/null +++ b/vendor/go.uber.org/goleak/ya.make @@ -0,0 +1,27 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + doc.go + leaks.go + options.go + testmain.go + tracestack_new.go +) + +GO_TEST_SRCS( + leaks_test.go + options_test.go + testmain_test.go + utils_test.go +) + +GO_XTEST_SRCS(signal_test.go) + +END() + +RECURSE( + gotest + internal +) diff --git a/vendor/go.uber.org/multierr/appendinvoke_example_test.go b/vendor/go.uber.org/multierr/appendinvoke_example_test.go new file mode 100644 index 0000000000..f8b674bd35 --- /dev/null +++ b/vendor/go.uber.org/multierr/appendinvoke_example_test.go @@ -0,0 +1,73 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package multierr_test + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "go.uber.org/multierr" +) + +func ExampleAppendInvoke() { + if err := run(); err != nil { + log.Fatal(err) + } +} + +func run() (err error) { + dir, err := os.MkdirTemp("", "multierr") + // We create a temporary directory and defer its deletion when this + // function returns. + // + // If we failed to delete the temporary directory, we append its + // failure into the returned value with multierr.AppendInvoke. + // + // This uses a custom invoker that we implement below. + defer multierr.AppendInvoke(&err, RemoveAll(dir)) + + path := filepath.Join(dir, "example.txt") + f, err := os.Create(path) + if err != nil { + return err + } + // Similarly, we defer closing the open file when the function returns, + // and appends its failure, if any, into the returned error. + // + // This uses the multierr.Close invoker included in multierr. + defer multierr.AppendInvoke(&err, multierr.Close(f)) + + if _, err := fmt.Fprintln(f, "hello"); err != nil { + return err + } + + return nil +} + +// RemoveAll is a multierr.Invoker that deletes the provided directory and all +// of its contents. +type RemoveAll string + +func (r RemoveAll) Invoke() error { + return os.RemoveAll(string(r)) +} diff --git a/vendor/go.uber.org/multierr/benchmarks_test.go b/vendor/go.uber.org/multierr/benchmarks_test.go new file mode 100644 index 0000000000..562d3bca04 --- /dev/null +++ b/vendor/go.uber.org/multierr/benchmarks_test.go @@ -0,0 +1,127 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package multierr + +import ( + "errors" + "fmt" + "testing" +) + +func BenchmarkAppend(b *testing.B) { + errorTypes := []struct { + name string + err error + }{ + { + name: "nil", + err: nil, + }, + { + name: "single error", + err: errors.New("test"), + }, + { + name: "multiple errors", + err: appendN(nil, errors.New("err"), 10), + }, + } + + for _, initial := range errorTypes { + for _, v := range errorTypes { + msg := fmt.Sprintf("append %v to %v", v.name, initial.name) + b.Run(msg, func(b *testing.B) { + for _, appends := range []int{1, 2, 10} { + b.Run(fmt.Sprint(appends), func(b *testing.B) { + for i := 0; i < b.N; i++ { + appendN(initial.err, v.err, appends) + } + }) + } + }) + } + } +} + +func BenchmarkCombine(b *testing.B) { + b.Run("inline 1", func(b *testing.B) { + var x error + for i := 0; i < b.N; i++ { + Combine(x) + } + }) + + b.Run("inline 2", func(b *testing.B) { + var x, y error + for i := 0; i < b.N; i++ { + Combine(x, y) + } + }) + + b.Run("inline 3 no error", func(b *testing.B) { + var x, y, z error + for i := 0; i < b.N; i++ { + Combine(x, y, z) + } + }) + + b.Run("inline 3 one error", func(b *testing.B) { + var x, y, z error + z = fmt.Errorf("failed") + for i := 0; i < b.N; i++ { + Combine(x, y, z) + } + }) + + b.Run("inline 3 multiple errors", func(b *testing.B) { + var x, y, z error + z = fmt.Errorf("failed3") + y = fmt.Errorf("failed2") + x = fmt.Errorf("failed") + for i := 0; i < b.N; i++ { + Combine(x, y, z) + } + }) + + b.Run("slice 100 no errors", func(b *testing.B) { + errs := make([]error, 100) + for i := 0; i < b.N; i++ { + Combine(errs...) + } + }) + + b.Run("slice 100 one error", func(b *testing.B) { + errs := make([]error, 100) + errs[len(errs)-1] = fmt.Errorf("failed") + for i := 0; i < b.N; i++ { + Combine(errs...) + } + }) + + b.Run("slice 100 multi error", func(b *testing.B) { + errs := make([]error, 100) + errs[0] = fmt.Errorf("failed1") + errs[len(errs)-1] = fmt.Errorf("failed2") + for i := 0; i < b.N; i++ { + Combine(errs...) + } + }) +} diff --git a/vendor/go.uber.org/multierr/error.go b/vendor/go.uber.org/multierr/error.go new file mode 100644 index 0000000000..3a828b2dff --- /dev/null +++ b/vendor/go.uber.org/multierr/error.go @@ -0,0 +1,646 @@ +// Copyright (c) 2017-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package multierr allows combining one or more errors together. +// +// # Overview +// +// Errors can be combined with the use of the Combine function. +// +// multierr.Combine( +// reader.Close(), +// writer.Close(), +// conn.Close(), +// ) +// +// If only two errors are being combined, the Append function may be used +// instead. +// +// err = multierr.Append(reader.Close(), writer.Close()) +// +// The underlying list of errors for a returned error object may be retrieved +// with the Errors function. +// +// errors := multierr.Errors(err) +// if len(errors) > 0 { +// fmt.Println("The following errors occurred:", errors) +// } +// +// # Appending from a loop +// +// You sometimes need to append into an error from a loop. +// +// var err error +// for _, item := range items { +// err = multierr.Append(err, process(item)) +// } +// +// Cases like this may require knowledge of whether an individual instance +// failed. This usually requires introduction of a new variable. +// +// var err error +// for _, item := range items { +// if perr := process(item); perr != nil { +// log.Warn("skipping item", item) +// err = multierr.Append(err, perr) +// } +// } +// +// multierr includes AppendInto to simplify cases like this. +// +// var err error +// for _, item := range items { +// if multierr.AppendInto(&err, process(item)) { +// log.Warn("skipping item", item) +// } +// } +// +// This will append the error into the err variable, and return true if that +// individual error was non-nil. +// +// See [AppendInto] for more information. +// +// # Deferred Functions +// +// Go makes it possible to modify the return value of a function in a defer +// block if the function was using named returns. This makes it possible to +// record resource cleanup failures from deferred blocks. +// +// func sendRequest(req Request) (err error) { +// conn, err := openConnection() +// if err != nil { +// return err +// } +// defer func() { +// err = multierr.Append(err, conn.Close()) +// }() +// // ... +// } +// +// multierr provides the Invoker type and AppendInvoke function to make cases +// like the above simpler and obviate the need for a closure. The following is +// roughly equivalent to the example above. +// +// func sendRequest(req Request) (err error) { +// conn, err := openConnection() +// if err != nil { +// return err +// } +// defer multierr.AppendInvoke(&err, multierr.Close(conn)) +// // ... +// } +// +// See [AppendInvoke] and [Invoker] for more information. +// +// NOTE: If you're modifying an error from inside a defer, you MUST use a named +// return value for that function. +// +// # Advanced Usage +// +// Errors returned by Combine and Append MAY implement the following +// interface. +// +// type errorGroup interface { +// // Returns a slice containing the underlying list of errors. +// // +// // This slice MUST NOT be modified by the caller. +// Errors() []error +// } +// +// Note that if you need access to list of errors behind a multierr error, you +// should prefer using the Errors function. That said, if you need cheap +// read-only access to the underlying errors slice, you can attempt to cast +// the error to this interface. You MUST handle the failure case gracefully +// because errors returned by Combine and Append are not guaranteed to +// implement this interface. +// +// var errors []error +// group, ok := err.(errorGroup) +// if ok { +// errors = group.Errors() +// } else { +// errors = []error{err} +// } +package multierr // import "go.uber.org/multierr" + +import ( + "bytes" + "errors" + "fmt" + "io" + "strings" + "sync" + "sync/atomic" +) + +var ( + // Separator for single-line error messages. + _singlelineSeparator = []byte("; ") + + // Prefix for multi-line messages + _multilinePrefix = []byte("the following errors occurred:") + + // Prefix for the first and following lines of an item in a list of + // multi-line error messages. + // + // For example, if a single item is: + // + // foo + // bar + // + // It will become, + // + // - foo + // bar + _multilineSeparator = []byte("\n - ") + _multilineIndent = []byte(" ") +) + +// _bufferPool is a pool of bytes.Buffers. +var _bufferPool = sync.Pool{ + New: func() interface{} { + return &bytes.Buffer{} + }, +} + +type errorGroup interface { + Errors() []error +} + +// Errors returns a slice containing zero or more errors that the supplied +// error is composed of. If the error is nil, a nil slice is returned. +// +// err := multierr.Append(r.Close(), w.Close()) +// errors := multierr.Errors(err) +// +// If the error is not composed of other errors, the returned slice contains +// just the error that was passed in. +// +// Callers of this function are free to modify the returned slice. +func Errors(err error) []error { + return extractErrors(err) +} + +// multiError is an error that holds one or more errors. +// +// An instance of this is guaranteed to be non-empty and flattened. That is, +// none of the errors inside multiError are other multiErrors. +// +// multiError formats to a semi-colon delimited list of error messages with +// %v and with a more readable multi-line format with %+v. +type multiError struct { + copyNeeded atomic.Bool + errors []error +} + +// Errors returns the list of underlying errors. +// +// This slice MUST NOT be modified. +func (merr *multiError) Errors() []error { + if merr == nil { + return nil + } + return merr.errors +} + +func (merr *multiError) Error() string { + if merr == nil { + return "" + } + + buff := _bufferPool.Get().(*bytes.Buffer) + buff.Reset() + + merr.writeSingleline(buff) + + result := buff.String() + _bufferPool.Put(buff) + return result +} + +// Every compares every error in the given err against the given target error +// using [errors.Is], and returns true only if every comparison returned true. +func Every(err error, target error) bool { + for _, e := range extractErrors(err) { + if !errors.Is(e, target) { + return false + } + } + return true +} + +func (merr *multiError) Format(f fmt.State, c rune) { + if c == 'v' && f.Flag('+') { + merr.writeMultiline(f) + } else { + merr.writeSingleline(f) + } +} + +func (merr *multiError) writeSingleline(w io.Writer) { + first := true + for _, item := range merr.errors { + if first { + first = false + } else { + w.Write(_singlelineSeparator) + } + io.WriteString(w, item.Error()) + } +} + +func (merr *multiError) writeMultiline(w io.Writer) { + w.Write(_multilinePrefix) + for _, item := range merr.errors { + w.Write(_multilineSeparator) + writePrefixLine(w, _multilineIndent, fmt.Sprintf("%+v", item)) + } +} + +// Writes s to the writer with the given prefix added before each line after +// the first. +func writePrefixLine(w io.Writer, prefix []byte, s string) { + first := true + for len(s) > 0 { + if first { + first = false + } else { + w.Write(prefix) + } + + idx := strings.IndexByte(s, '\n') + if idx < 0 { + idx = len(s) - 1 + } + + io.WriteString(w, s[:idx+1]) + s = s[idx+1:] + } +} + +type inspectResult struct { + // Number of top-level non-nil errors + Count int + + // Total number of errors including multiErrors + Capacity int + + // Index of the first non-nil error in the list. Value is meaningless if + // Count is zero. + FirstErrorIdx int + + // Whether the list contains at least one multiError + ContainsMultiError bool +} + +// Inspects the given slice of errors so that we can efficiently allocate +// space for it. +func inspect(errors []error) (res inspectResult) { + first := true + for i, err := range errors { + if err == nil { + continue + } + + res.Count++ + if first { + first = false + res.FirstErrorIdx = i + } + + if merr, ok := err.(*multiError); ok { + res.Capacity += len(merr.errors) + res.ContainsMultiError = true + } else { + res.Capacity++ + } + } + return +} + +// fromSlice converts the given list of errors into a single error. +func fromSlice(errors []error) error { + // Don't pay to inspect small slices. + switch len(errors) { + case 0: + return nil + case 1: + return errors[0] + } + + res := inspect(errors) + switch res.Count { + case 0: + return nil + case 1: + // only one non-nil entry + return errors[res.FirstErrorIdx] + case len(errors): + if !res.ContainsMultiError { + // Error list is flat. Make a copy of it + // Otherwise "errors" escapes to the heap + // unconditionally for all other cases. + // This lets us optimize for the "no errors" case. + out := append(([]error)(nil), errors...) + return &multiError{errors: out} + } + } + + nonNilErrs := make([]error, 0, res.Capacity) + for _, err := range errors[res.FirstErrorIdx:] { + if err == nil { + continue + } + + if nested, ok := err.(*multiError); ok { + nonNilErrs = append(nonNilErrs, nested.errors...) + } else { + nonNilErrs = append(nonNilErrs, err) + } + } + + return &multiError{errors: nonNilErrs} +} + +// Combine combines the passed errors into a single error. +// +// If zero arguments were passed or if all items are nil, a nil error is +// returned. +// +// Combine(nil, nil) // == nil +// +// If only a single error was passed, it is returned as-is. +// +// Combine(err) // == err +// +// Combine skips over nil arguments so this function may be used to combine +// together errors from operations that fail independently of each other. +// +// multierr.Combine( +// reader.Close(), +// writer.Close(), +// pipe.Close(), +// ) +// +// If any of the passed errors is a multierr error, it will be flattened along +// with the other errors. +// +// multierr.Combine(multierr.Combine(err1, err2), err3) +// // is the same as +// multierr.Combine(err1, err2, err3) +// +// The returned error formats into a readable multi-line error message if +// formatted with %+v. +// +// fmt.Sprintf("%+v", multierr.Combine(err1, err2)) +func Combine(errors ...error) error { + return fromSlice(errors) +} + +// Append appends the given errors together. Either value may be nil. +// +// This function is a specialization of Combine for the common case where +// there are only two errors. +// +// err = multierr.Append(reader.Close(), writer.Close()) +// +// The following pattern may also be used to record failure of deferred +// operations without losing information about the original error. +// +// func doSomething(..) (err error) { +// f := acquireResource() +// defer func() { +// err = multierr.Append(err, f.Close()) +// }() +// +// Note that the variable MUST be a named return to append an error to it from +// the defer statement. See also [AppendInvoke]. +func Append(left error, right error) error { + switch { + case left == nil: + return right + case right == nil: + return left + } + + if _, ok := right.(*multiError); !ok { + if l, ok := left.(*multiError); ok && !l.copyNeeded.Swap(true) { + // Common case where the error on the left is constantly being + // appended to. + errs := append(l.errors, right) + return &multiError{errors: errs} + } else if !ok { + // Both errors are single errors. + return &multiError{errors: []error{left, right}} + } + } + + // Either right or both, left and right, are multiErrors. Rely on usual + // expensive logic. + errors := [2]error{left, right} + return fromSlice(errors[0:]) +} + +// AppendInto appends an error into the destination of an error pointer and +// returns whether the error being appended was non-nil. +// +// var err error +// multierr.AppendInto(&err, r.Close()) +// multierr.AppendInto(&err, w.Close()) +// +// The above is equivalent to, +// +// err := multierr.Append(r.Close(), w.Close()) +// +// As AppendInto reports whether the provided error was non-nil, it may be +// used to build a multierr error in a loop more ergonomically. For example: +// +// var err error +// for line := range lines { +// var item Item +// if multierr.AppendInto(&err, parse(line, &item)) { +// continue +// } +// items = append(items, item) +// } +// +// Compare this with a version that relies solely on Append: +// +// var err error +// for line := range lines { +// var item Item +// if parseErr := parse(line, &item); parseErr != nil { +// err = multierr.Append(err, parseErr) +// continue +// } +// items = append(items, item) +// } +func AppendInto(into *error, err error) (errored bool) { + if into == nil { + // We panic if 'into' is nil. This is not documented above + // because suggesting that the pointer must be non-nil may + // confuse users into thinking that the error that it points + // to must be non-nil. + panic("misuse of multierr.AppendInto: into pointer must not be nil") + } + + if err == nil { + return false + } + *into = Append(*into, err) + return true +} + +// Invoker is an operation that may fail with an error. Use it with +// AppendInvoke to append the result of calling the function into an error. +// This allows you to conveniently defer capture of failing operations. +// +// See also, [Close] and [Invoke]. +type Invoker interface { + Invoke() error +} + +// Invoke wraps a function which may fail with an error to match the Invoker +// interface. Use it to supply functions matching this signature to +// AppendInvoke. +// +// For example, +// +// func processReader(r io.Reader) (err error) { +// scanner := bufio.NewScanner(r) +// defer multierr.AppendInvoke(&err, multierr.Invoke(scanner.Err)) +// for scanner.Scan() { +// // ... +// } +// // ... +// } +// +// In this example, the following line will construct the Invoker right away, +// but defer the invocation of scanner.Err() until the function returns. +// +// defer multierr.AppendInvoke(&err, multierr.Invoke(scanner.Err)) +// +// Note that the error you're appending to from the defer statement MUST be a +// named return. +type Invoke func() error + +// Invoke calls the supplied function and returns its result. +func (i Invoke) Invoke() error { return i() } + +// Close builds an Invoker that closes the provided io.Closer. Use it with +// AppendInvoke to close io.Closers and append their results into an error. +// +// For example, +// +// func processFile(path string) (err error) { +// f, err := os.Open(path) +// if err != nil { +// return err +// } +// defer multierr.AppendInvoke(&err, multierr.Close(f)) +// return processReader(f) +// } +// +// In this example, multierr.Close will construct the Invoker right away, but +// defer the invocation of f.Close until the function returns. +// +// defer multierr.AppendInvoke(&err, multierr.Close(f)) +// +// Note that the error you're appending to from the defer statement MUST be a +// named return. +func Close(closer io.Closer) Invoker { + return Invoke(closer.Close) +} + +// AppendInvoke appends the result of calling the given Invoker into the +// provided error pointer. Use it with named returns to safely defer +// invocation of fallible operations until a function returns, and capture the +// resulting errors. +// +// func doSomething(...) (err error) { +// // ... +// f, err := openFile(..) +// if err != nil { +// return err +// } +// +// // multierr will call f.Close() when this function returns and +// // if the operation fails, its append its error into the +// // returned error. +// defer multierr.AppendInvoke(&err, multierr.Close(f)) +// +// scanner := bufio.NewScanner(f) +// // Similarly, this scheduled scanner.Err to be called and +// // inspected when the function returns and append its error +// // into the returned error. +// defer multierr.AppendInvoke(&err, multierr.Invoke(scanner.Err)) +// +// // ... +// } +// +// NOTE: If used with a defer, the error variable MUST be a named return. +// +// Without defer, AppendInvoke behaves exactly like AppendInto. +// +// err := // ... +// multierr.AppendInvoke(&err, mutltierr.Invoke(foo)) +// +// // ...is roughly equivalent to... +// +// err := // ... +// multierr.AppendInto(&err, foo()) +// +// The advantage of the indirection introduced by Invoker is to make it easy +// to defer the invocation of a function. Without this indirection, the +// invoked function will be evaluated at the time of the defer block rather +// than when the function returns. +// +// // BAD: This is likely not what the caller intended. This will evaluate +// // foo() right away and append its result into the error when the +// // function returns. +// defer multierr.AppendInto(&err, foo()) +// +// // GOOD: This will defer invocation of foo unutil the function returns. +// defer multierr.AppendInvoke(&err, multierr.Invoke(foo)) +// +// multierr provides a few Invoker implementations out of the box for +// convenience. See [Invoker] for more information. +func AppendInvoke(into *error, invoker Invoker) { + AppendInto(into, invoker.Invoke()) +} + +// AppendFunc is a shorthand for [AppendInvoke]. +// It allows using function or method value directly +// without having to wrap it into an [Invoker] interface. +// +// func doSomething(...) (err error) { +// w, err := startWorker(...) +// if err != nil { +// return err +// } +// +// // multierr will call w.Stop() when this function returns and +// // if the operation fails, it appends its error into the +// // returned error. +// defer multierr.AppendFunc(&err, w.Stop) +// } +func AppendFunc(into *error, fn func() error) { + AppendInvoke(into, Invoke(fn)) +} diff --git a/vendor/go.uber.org/multierr/error_ext_test.go b/vendor/go.uber.org/multierr/error_ext_test.go new file mode 100644 index 0000000000..9936e361bc --- /dev/null +++ b/vendor/go.uber.org/multierr/error_ext_test.go @@ -0,0 +1,142 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package multierr_test + +import ( + "errors" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/multierr" +) + +type errGreatSadness struct{ id int } + +func (errGreatSadness) Error() string { + return "great sadness" +} + +type errUnprecedentedFailure struct{ id int } + +func (errUnprecedentedFailure) Error() string { + return "unprecedented failure" +} + +func (e errUnprecedentedFailure) Unwrap() error { + return errRootCause{e.id} +} + +type errRootCause struct{ i int } + +func (errRootCause) Error() string { + return "root cause" +} + +func TestErrorsWrapping(t *testing.T) { + err := multierr.Append( + errGreatSadness{42}, + errUnprecedentedFailure{43}, + ) + + t.Run("left", func(t *testing.T) { + t.Run("As", func(t *testing.T) { + var got errGreatSadness + require.True(t, errors.As(err, &got)) + assert.Equal(t, 42, got.id) + }) + + t.Run("Is", func(t *testing.T) { + assert.False(t, errors.Is(err, errGreatSadness{41})) + assert.True(t, errors.Is(err, errGreatSadness{42})) + }) + }) + + t.Run("right", func(t *testing.T) { + t.Run("As", func(t *testing.T) { + var got errUnprecedentedFailure + require.True(t, errors.As(err, &got)) + assert.Equal(t, 43, got.id) + }) + + t.Run("Is", func(t *testing.T) { + assert.False(t, errors.Is(err, errUnprecedentedFailure{42})) + assert.True(t, errors.Is(err, errUnprecedentedFailure{43})) + }) + }) + + t.Run("top-level", func(t *testing.T) { + t.Run("As", func(t *testing.T) { + var got interface{ Errors() []error } + require.True(t, errors.As(err, &got)) + assert.Len(t, got.Errors(), 2) + }) + + t.Run("Is", func(t *testing.T) { + assert.True(t, errors.Is(err, err)) + }) + }) + + t.Run("root cause", func(t *testing.T) { + t.Run("As", func(t *testing.T) { + var got errRootCause + require.True(t, errors.As(err, &got)) + assert.Equal(t, 43, got.i) + }) + + t.Run("Is", func(t *testing.T) { + assert.False(t, errors.Is(err, errRootCause{42})) + assert.True(t, errors.Is(err, errRootCause{43})) + }) + }) + + t.Run("mismatch", func(t *testing.T) { + t.Run("As", func(t *testing.T) { + var got *os.PathError + assert.False(t, errors.As(err, &got)) + }) + + t.Run("Is", func(t *testing.T) { + assert.False(t, errors.Is(err, errors.New("great sadness"))) + }) + }) +} + +func TestErrorsWrappingSameType(t *testing.T) { + err := multierr.Combine( + errGreatSadness{1}, + errGreatSadness{2}, + errGreatSadness{3}, + ) + + t.Run("As returns first", func(t *testing.T) { + var got errGreatSadness + require.True(t, errors.As(err, &got)) + assert.Equal(t, 1, got.id) + }) + + t.Run("Is matches all", func(t *testing.T) { + assert.True(t, errors.Is(err, errGreatSadness{1})) + assert.True(t, errors.Is(err, errGreatSadness{2})) + assert.True(t, errors.Is(err, errGreatSadness{3})) + }) +} diff --git a/vendor/go.uber.org/multierr/error_post_go120.go b/vendor/go.uber.org/multierr/error_post_go120.go new file mode 100644 index 0000000000..a173f9c251 --- /dev/null +++ b/vendor/go.uber.org/multierr/error_post_go120.go @@ -0,0 +1,48 @@ +// Copyright (c) 2017-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//go:build go1.20 +// +build go1.20 + +package multierr + +// Unwrap returns a list of errors wrapped by this multierr. +func (merr *multiError) Unwrap() []error { + return merr.Errors() +} + +type multipleErrors interface { + Unwrap() []error +} + +func extractErrors(err error) []error { + if err == nil { + return nil + } + + // check if the given err is an Unwrapable error that + // implements multipleErrors interface. + eg, ok := err.(multipleErrors) + if !ok { + return []error{err} + } + + return append(([]error)(nil), eg.Unwrap()...) +} diff --git a/vendor/go.uber.org/multierr/error_post_go120_test.go b/vendor/go.uber.org/multierr/error_post_go120_test.go new file mode 100644 index 0000000000..39873c2f22 --- /dev/null +++ b/vendor/go.uber.org/multierr/error_post_go120_test.go @@ -0,0 +1,65 @@ +// Copyright (c) 2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//go:build go1.20 +// +build go1.20 + +package multierr + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestErrorsOnErrorsJoin(t *testing.T) { + err1 := errors.New("err1") + err2 := errors.New("err2") + err := errors.Join(err1, err2) + + errs := Errors(err) + assert.Equal(t, 2, len(errs)) + assert.Equal(t, err1, errs[0]) + assert.Equal(t, err2, errs[1]) +} + +func TestEveryWithErrorsJoin(t *testing.T) { + myError1 := errors.New("woeful misfortune") + myError2 := errors.New("worrisome travesty") + + t.Run("all match", func(t *testing.T) { + err := errors.Join(myError1, myError1, myError1) + + assert.True(t, errors.Is(err, myError1)) + assert.True(t, Every(err, myError1)) + assert.False(t, errors.Is(err, myError2)) + assert.False(t, Every(err, myError2)) + }) + + t.Run("one matches", func(t *testing.T) { + err := errors.Join(myError1, myError2) + + assert.True(t, errors.Is(err, myError1)) + assert.False(t, Every(err, myError1)) + assert.True(t, errors.Is(err, myError2)) + assert.False(t, Every(err, myError2)) + }) +} diff --git a/vendor/go.uber.org/multierr/error_test.go b/vendor/go.uber.org/multierr/error_test.go new file mode 100644 index 0000000000..2e02d043a5 --- /dev/null +++ b/vendor/go.uber.org/multierr/error_test.go @@ -0,0 +1,779 @@ +// Copyright (c) 2017-2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package multierr + +import ( + "errors" + "fmt" + "io" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// richFormatError is an error that prints a different output depending on +// whether %v or %+v was used. +type richFormatError struct{} + +func (r richFormatError) Error() string { + return fmt.Sprint(r) +} + +func (richFormatError) Format(f fmt.State, c rune) { + if c == 'v' && f.Flag('+') { + io.WriteString(f, "multiline\nmessage\nwith plus") + } else { + io.WriteString(f, "without plus") + } +} + +func appendN(initial, err error, n int) error { + errs := initial + for i := 0; i < n; i++ { + errs = Append(errs, err) + } + return errs +} + +func newMultiErr(errors ...error) error { + return &multiError{errors: errors} +} + +func TestEvery(t *testing.T) { + myError1 := errors.New("woeful misfortune") + myError2 := errors.New("worrisome travesty") + + for _, tt := range []struct { + desc string + giveErr error + giveTarget error + wantIs bool + wantEvery bool + }{ + { + desc: "all match", + giveErr: newMultiErr(myError1, myError1, myError1), + giveTarget: myError1, + wantIs: true, + wantEvery: true, + }, + { + desc: "one matches", + giveErr: newMultiErr(myError1, myError2), + giveTarget: myError1, + wantIs: true, + wantEvery: false, + }, + { + desc: "not multiErrs and non equal", + giveErr: myError1, + giveTarget: myError2, + wantIs: false, + wantEvery: false, + }, + { + desc: "not multiErrs but equal", + giveErr: myError1, + giveTarget: myError1, + wantIs: true, + wantEvery: true, + }, + { + desc: "not multiErr w multiErr target", + giveErr: myError1, + giveTarget: newMultiErr(myError1, myError1), + wantIs: false, + wantEvery: false, + }, + { + desc: "multiErr w multiErr target", + giveErr: newMultiErr(myError1, myError1), + giveTarget: newMultiErr(myError1, myError1), + wantIs: false, + wantEvery: false, + }, + } { + t.Run(tt.desc, func(t *testing.T) { + assert.Equal(t, tt.wantIs, errors.Is(tt.giveErr, tt.giveTarget)) + assert.Equal(t, tt.wantEvery, Every(tt.giveErr, tt.giveTarget)) + }) + } +} + +func TestCombine(t *testing.T) { + tests := []struct { + // Input + giveErrors []error + + // Resulting error + wantError error + + // %+v and %v string representations + wantMultiline string + wantSingleline string + }{ + { + giveErrors: nil, + wantError: nil, + }, + { + giveErrors: []error{}, + wantError: nil, + }, + { + giveErrors: []error{ + errors.New("foo"), + nil, + newMultiErr( + errors.New("bar"), + ), + nil, + }, + wantError: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + wantMultiline: "the following errors occurred:\n" + + " - foo\n" + + " - bar", + wantSingleline: "foo; bar", + }, + { + giveErrors: []error{nil, nil, errors.New("great sadness"), nil}, + wantError: errors.New("great sadness"), + wantMultiline: "great sadness", + wantSingleline: "great sadness", + }, + { + giveErrors: []error{ + errors.New("foo"), + newMultiErr( + errors.New("bar"), + ), + }, + wantError: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + wantMultiline: "the following errors occurred:\n" + + " - foo\n" + + " - bar", + wantSingleline: "foo; bar", + }, + { + giveErrors: []error{errors.New("great sadness")}, + wantError: errors.New("great sadness"), + wantMultiline: "great sadness", + wantSingleline: "great sadness", + }, + { + giveErrors: []error{ + errors.New("foo"), + errors.New("bar"), + }, + wantError: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + wantMultiline: "the following errors occurred:\n" + + " - foo\n" + + " - bar", + wantSingleline: "foo; bar", + }, + { + giveErrors: []error{ + errors.New("great sadness"), + errors.New("multi\n line\nerror message"), + errors.New("single line error message"), + }, + wantError: newMultiErr( + errors.New("great sadness"), + errors.New("multi\n line\nerror message"), + errors.New("single line error message"), + ), + wantMultiline: "the following errors occurred:\n" + + " - great sadness\n" + + " - multi\n" + + " line\n" + + " error message\n" + + " - single line error message", + wantSingleline: "great sadness; " + + "multi\n line\nerror message; " + + "single line error message", + }, + { + giveErrors: []error{ + errors.New("foo"), + newMultiErr( + errors.New("bar"), + errors.New("baz"), + ), + errors.New("qux"), + }, + wantError: newMultiErr( + errors.New("foo"), + errors.New("bar"), + errors.New("baz"), + errors.New("qux"), + ), + wantMultiline: "the following errors occurred:\n" + + " - foo\n" + + " - bar\n" + + " - baz\n" + + " - qux", + wantSingleline: "foo; bar; baz; qux", + }, + { + giveErrors: []error{ + errors.New("foo"), + nil, + newMultiErr( + errors.New("bar"), + ), + nil, + }, + wantError: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + wantMultiline: "the following errors occurred:\n" + + " - foo\n" + + " - bar", + wantSingleline: "foo; bar", + }, + { + giveErrors: []error{ + errors.New("foo"), + newMultiErr( + errors.New("bar"), + ), + }, + wantError: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + wantMultiline: "the following errors occurred:\n" + + " - foo\n" + + " - bar", + wantSingleline: "foo; bar", + }, + { + giveErrors: []error{ + errors.New("foo"), + richFormatError{}, + errors.New("bar"), + }, + wantError: newMultiErr( + errors.New("foo"), + richFormatError{}, + errors.New("bar"), + ), + wantMultiline: "the following errors occurred:\n" + + " - foo\n" + + " - multiline\n" + + " message\n" + + " with plus\n" + + " - bar", + wantSingleline: "foo; without plus; bar", + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + err := Combine(tt.giveErrors...) + require.Equal(t, tt.wantError, err) + + if tt.wantMultiline != "" { + t.Run("Sprintf/multiline", func(t *testing.T) { + assert.Equal(t, tt.wantMultiline, fmt.Sprintf("%+v", err)) + }) + } + + if tt.wantSingleline != "" { + t.Run("Sprintf/singleline", func(t *testing.T) { + assert.Equal(t, tt.wantSingleline, fmt.Sprintf("%v", err)) + }) + + t.Run("Error()", func(t *testing.T) { + assert.Equal(t, tt.wantSingleline, err.Error()) + }) + + if s, ok := err.(fmt.Stringer); ok { + t.Run("String()", func(t *testing.T) { + assert.Equal(t, tt.wantSingleline, s.String()) + }) + } + } + }) + } +} + +func TestCombineDoesNotModifySlice(t *testing.T) { + errors := []error{ + errors.New("foo"), + nil, + errors.New("bar"), + } + + assert.NotNil(t, Combine(errors...)) + assert.Len(t, errors, 3) + assert.Nil(t, errors[1], 3) +} + +func TestCombineGoodCaseNoAlloc(t *testing.T) { + errs := make([]error, 10) + allocs := testing.AllocsPerRun(100, func() { + Combine(errs...) + }) + assert.Equal(t, 0.0, allocs) +} + +func TestAppend(t *testing.T) { + tests := []struct { + left error + right error + want error + }{ + { + left: nil, + right: nil, + want: nil, + }, + { + left: nil, + right: errors.New("great sadness"), + want: errors.New("great sadness"), + }, + { + left: errors.New("great sadness"), + right: nil, + want: errors.New("great sadness"), + }, + { + left: errors.New("foo"), + right: errors.New("bar"), + want: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + }, + { + left: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + right: errors.New("baz"), + want: newMultiErr( + errors.New("foo"), + errors.New("bar"), + errors.New("baz"), + ), + }, + { + left: errors.New("baz"), + right: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + want: newMultiErr( + errors.New("baz"), + errors.New("foo"), + errors.New("bar"), + ), + }, + { + left: newMultiErr( + errors.New("foo"), + ), + right: newMultiErr( + errors.New("bar"), + ), + want: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + }, + } + + for _, tt := range tests { + assert.Equal(t, tt.want, Append(tt.left, tt.right)) + } +} + +type notMultiErr struct{} + +var _ errorGroup = notMultiErr{} + +func (notMultiErr) Error() string { + return "great sadness" +} + +func (notMultiErr) Errors() []error { + return []error{errors.New("great sadness")} +} + +func TestErrors(t *testing.T) { + tests := []struct { + give error + want []error + + // Don't attempt to cast to errorGroup or *multiError + dontCast bool + }{ + {dontCast: true}, // nil + { + give: errors.New("hi"), + want: []error{errors.New("hi")}, + dontCast: true, + }, + { + // We don't yet support non-multierr errors that do + // not implement Unwrap() []error. + give: notMultiErr{}, + want: []error{notMultiErr{}}, + dontCast: true, + }, + { + give: Combine( + errors.New("foo"), + errors.New("bar"), + ), + want: []error{ + errors.New("foo"), + errors.New("bar"), + }, + }, + { + give: Append( + errors.New("foo"), + errors.New("bar"), + ), + want: []error{ + errors.New("foo"), + errors.New("bar"), + }, + }, + { + give: Append( + errors.New("foo"), + Combine( + errors.New("bar"), + ), + ), + want: []error{ + errors.New("foo"), + errors.New("bar"), + }, + }, + { + give: Combine( + errors.New("foo"), + Append( + errors.New("bar"), + errors.New("baz"), + ), + errors.New("qux"), + ), + want: []error{ + errors.New("foo"), + errors.New("bar"), + errors.New("baz"), + errors.New("qux"), + }, + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + t.Run("Errors()", func(t *testing.T) { + require.Equal(t, tt.want, Errors(tt.give)) + }) + + if tt.dontCast { + return + } + + t.Run("multiError", func(t *testing.T) { + require.Equal(t, tt.want, tt.give.(*multiError).Errors()) + }) + + t.Run("errorGroup", func(t *testing.T) { + require.Equal(t, tt.want, tt.give.(errorGroup).Errors()) + }) + }) + } +} + +func createMultiErrWithCapacity() error { + // Create a multiError that has capacity for more errors so Append will + // modify the underlying array that may be shared. + return appendN(nil, errors.New("append"), 50) +} + +func TestAppendDoesNotModify(t *testing.T) { + initial := createMultiErrWithCapacity() + err1 := Append(initial, errors.New("err1")) + err2 := Append(initial, errors.New("err2")) + + // Make sure the error messages match, since we do modify the copyNeeded + // atomic, the values cannot be compared. + assert.EqualError(t, initial, createMultiErrWithCapacity().Error(), "Initial should not be modified") + + assert.EqualError(t, err1, Append(createMultiErrWithCapacity(), errors.New("err1")).Error()) + assert.EqualError(t, err2, Append(createMultiErrWithCapacity(), errors.New("err2")).Error()) +} + +func TestAppendRace(t *testing.T) { + initial := createMultiErrWithCapacity() + + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + + err := initial + for j := 0; j < 10; j++ { + err = Append(err, errors.New("err")) + } + }() + } + + wg.Wait() +} + +func TestErrorsSliceIsImmutable(t *testing.T) { + err1 := errors.New("err1") + err2 := errors.New("err2") + + err := Append(err1, err2) + gotErrors := Errors(err) + require.Equal(t, []error{err1, err2}, gotErrors, "errors must match") + + gotErrors[0] = nil + gotErrors[1] = errors.New("err3") + + require.Equal(t, []error{err1, err2}, Errors(err), + "errors must match after modification") +} + +func TestNilMultierror(t *testing.T) { + // For safety, all operations on multiError should be safe even if it is + // nil. + var err *multiError + + require.Empty(t, err.Error()) + require.Empty(t, err.Errors()) +} + +func TestAppendInto(t *testing.T) { + tests := []struct { + desc string + into *error + give error + want error + }{ + { + desc: "append into empty", + into: new(error), + give: errors.New("foo"), + want: errors.New("foo"), + }, + { + desc: "append into non-empty, non-multierr", + into: errorPtr(errors.New("foo")), + give: errors.New("bar"), + want: Combine( + errors.New("foo"), + errors.New("bar"), + ), + }, + { + desc: "append into non-empty multierr", + into: errorPtr(Combine( + errors.New("foo"), + errors.New("bar"), + )), + give: errors.New("baz"), + want: Combine( + errors.New("foo"), + errors.New("bar"), + errors.New("baz"), + ), + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + assert.True(t, AppendInto(tt.into, tt.give)) + assert.Equal(t, tt.want, *tt.into) + }) + } +} + +func TestAppendInvoke(t *testing.T) { + tests := []struct { + desc string + into *error + give Invoker + want error + }{ + { + desc: "append into empty", + into: new(error), + give: Invoke(func() error { + return errors.New("foo") + }), + want: errors.New("foo"), + }, + { + desc: "append into non-empty, non-multierr", + into: errorPtr(errors.New("foo")), + give: Invoke(func() error { + return errors.New("bar") + }), + want: Combine( + errors.New("foo"), + errors.New("bar"), + ), + }, + { + desc: "append into non-empty multierr", + into: errorPtr(Combine( + errors.New("foo"), + errors.New("bar"), + )), + give: Invoke(func() error { + return errors.New("baz") + }), + want: Combine( + errors.New("foo"), + errors.New("bar"), + errors.New("baz"), + ), + }, + { + desc: "close/empty", + into: new(error), + give: Close(newCloserMock(t, errors.New("foo"))), + want: errors.New("foo"), + }, + { + desc: "close/no fail", + into: new(error), + give: Close(newCloserMock(t, nil)), + want: nil, + }, + { + desc: "close/non-empty", + into: errorPtr(errors.New("foo")), + give: Close(newCloserMock(t, errors.New("bar"))), + want: Combine( + errors.New("foo"), + errors.New("bar"), + ), + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + AppendInvoke(tt.into, tt.give) + assert.Equal(t, tt.want, *tt.into) + }) + } +} + +func TestClose(t *testing.T) { + t.Run("fail", func(t *testing.T) { + give := errors.New("great sadness") + got := Close(newCloserMock(t, give)).Invoke() + assert.Same(t, give, got) + }) + + t.Run("success", func(t *testing.T) { + got := Close(newCloserMock(t, nil)).Invoke() + assert.Nil(t, got) + }) +} + +func TestAppendIntoNil(t *testing.T) { + t.Run("nil pointer panics", func(t *testing.T) { + assert.Panics(t, func() { + AppendInto(nil, errors.New("foo")) + }) + }) + + t.Run("nil error is no-op", func(t *testing.T) { + t.Run("empty left", func(t *testing.T) { + var err error + assert.False(t, AppendInto(&err, nil)) + assert.Nil(t, err) + }) + + t.Run("non-empty left", func(t *testing.T) { + err := errors.New("foo") + assert.False(t, AppendInto(&err, nil)) + assert.Equal(t, errors.New("foo"), err) + }) + }) +} + +func TestAppendFunc(t *testing.T) { + var ( + errDeferred = errors.New("deferred func called") + errOriginal = errors.New("original error") + ) + + stopFunc := func() error { + return errDeferred + } + + err := func() (err error) { + defer AppendFunc(&err, stopFunc) + + return errOriginal + }() + assert.Equal(t, []error{errOriginal, errDeferred}, Errors(err), "both deferred and original error must be returned") +} + +func errorPtr(err error) *error { + return &err +} + +type closerMock func() error + +func (c closerMock) Close() error { + return c() +} + +func newCloserMock(tb testing.TB, err error) io.Closer { + var closed bool + tb.Cleanup(func() { + if !closed { + tb.Error("closerMock wasn't closed before test end") + } + }) + return closerMock(func() error { + closed = true + return err + }) +} diff --git a/vendor/go.uber.org/multierr/example_test.go b/vendor/go.uber.org/multierr/example_test.go new file mode 100644 index 0000000000..e46a63328e --- /dev/null +++ b/vendor/go.uber.org/multierr/example_test.go @@ -0,0 +1,124 @@ +// Copyright (c) 2017-2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package multierr_test + +import ( + "errors" + "fmt" + "io" + + "go.uber.org/multierr" +) + +func ExampleCombine() { + err := multierr.Combine( + errors.New("call 1 failed"), + nil, // successful request + errors.New("call 3 failed"), + nil, // successful request + errors.New("call 5 failed"), + ) + fmt.Printf("%+v", err) + // Output: + // the following errors occurred: + // - call 1 failed + // - call 3 failed + // - call 5 failed +} + +func ExampleAppend() { + var err error + err = multierr.Append(err, errors.New("call 1 failed")) + err = multierr.Append(err, errors.New("call 2 failed")) + fmt.Println(err) + // Output: + // call 1 failed; call 2 failed +} + +func ExampleErrors() { + err := multierr.Combine( + nil, // successful request + errors.New("call 2 failed"), + errors.New("call 3 failed"), + ) + err = multierr.Append(err, nil) // successful request + err = multierr.Append(err, errors.New("call 5 failed")) + + errors := multierr.Errors(err) + for _, err := range errors { + fmt.Println(err) + } + // Output: + // call 2 failed + // call 3 failed + // call 5 failed +} + +func ExampleAppendInto() { + var err error + + if multierr.AppendInto(&err, errors.New("foo")) { + fmt.Println("call 1 failed") + } + + if multierr.AppendInto(&err, nil) { + fmt.Println("call 2 failed") + } + + if multierr.AppendInto(&err, errors.New("baz")) { + fmt.Println("call 3 failed") + } + + fmt.Println(err) + // Output: + // call 1 failed + // call 3 failed + // foo; baz +} + +type fakeCloser func() error + +func (f fakeCloser) Close() error { + return f() +} + +func FakeCloser(err error) io.Closer { + return fakeCloser(func() error { + return err + }) +} + +func ExampleClose() { + var err error + + closer := FakeCloser(errors.New("foo")) + + defer func() { + fmt.Println(err) + }() + defer multierr.AppendInvoke(&err, multierr.Close(closer)) + + fmt.Println("Hello, World") + + // Output: + // Hello, World + // foo +} diff --git a/vendor/go.uber.org/multierr/gotest/ya.make b/vendor/go.uber.org/multierr/gotest/ya.make new file mode 100644 index 0000000000..c91130db2c --- /dev/null +++ b/vendor/go.uber.org/multierr/gotest/ya.make @@ -0,0 +1,5 @@ +GO_TEST_FOR(vendor/go.uber.org/multierr) + +LICENSE(MIT) + +END() diff --git a/vendor/go.uber.org/multierr/ya.make b/vendor/go.uber.org/multierr/ya.make new file mode 100644 index 0000000000..d41165f4ec --- /dev/null +++ b/vendor/go.uber.org/multierr/ya.make @@ -0,0 +1,24 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + error.go + error_post_go120.go +) + +GO_TEST_SRCS( + benchmarks_test.go + error_post_go120_test.go + error_test.go +) + +GO_XTEST_SRCS( + appendinvoke_example_test.go + error_ext_test.go + example_test.go +) + +END() + +RECURSE(gotest) diff --git a/vendor/go.uber.org/zap/array.go b/vendor/go.uber.org/zap/array.go new file mode 100644 index 0000000000..abfccb566d --- /dev/null +++ b/vendor/go.uber.org/zap/array.go @@ -0,0 +1,447 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "fmt" + "time" + + "go.uber.org/zap/zapcore" +) + +// Array constructs a field with the given key and ArrayMarshaler. It provides +// a flexible, but still type-safe and efficient, way to add array-like types +// to the logging context. The struct's MarshalLogArray method is called lazily. +func Array(key string, val zapcore.ArrayMarshaler) Field { + return Field{Key: key, Type: zapcore.ArrayMarshalerType, Interface: val} +} + +// Bools constructs a field that carries a slice of bools. +func Bools(key string, bs []bool) Field { + return Array(key, bools(bs)) +} + +// ByteStrings constructs a field that carries a slice of []byte, each of which +// must be UTF-8 encoded text. +func ByteStrings(key string, bss [][]byte) Field { + return Array(key, byteStringsArray(bss)) +} + +// Complex128s constructs a field that carries a slice of complex numbers. +func Complex128s(key string, nums []complex128) Field { + return Array(key, complex128s(nums)) +} + +// Complex64s constructs a field that carries a slice of complex numbers. +func Complex64s(key string, nums []complex64) Field { + return Array(key, complex64s(nums)) +} + +// Durations constructs a field that carries a slice of time.Durations. +func Durations(key string, ds []time.Duration) Field { + return Array(key, durations(ds)) +} + +// Float64s constructs a field that carries a slice of floats. +func Float64s(key string, nums []float64) Field { + return Array(key, float64s(nums)) +} + +// Float32s constructs a field that carries a slice of floats. +func Float32s(key string, nums []float32) Field { + return Array(key, float32s(nums)) +} + +// Ints constructs a field that carries a slice of integers. +func Ints(key string, nums []int) Field { + return Array(key, ints(nums)) +} + +// Int64s constructs a field that carries a slice of integers. +func Int64s(key string, nums []int64) Field { + return Array(key, int64s(nums)) +} + +// Int32s constructs a field that carries a slice of integers. +func Int32s(key string, nums []int32) Field { + return Array(key, int32s(nums)) +} + +// Int16s constructs a field that carries a slice of integers. +func Int16s(key string, nums []int16) Field { + return Array(key, int16s(nums)) +} + +// Int8s constructs a field that carries a slice of integers. +func Int8s(key string, nums []int8) Field { + return Array(key, int8s(nums)) +} + +// Objects constructs a field with the given key, holding a list of the +// provided objects that can be marshaled by Zap. +// +// Note that these objects must implement zapcore.ObjectMarshaler directly. +// That is, if you're trying to marshal a []Request, the MarshalLogObject +// method must be declared on the Request type, not its pointer (*Request). +// If it's on the pointer, use ObjectValues. +// +// Given an object that implements MarshalLogObject on the value receiver, you +// can log a slice of those objects with Objects like so: +// +// type Author struct{ ... } +// func (a Author) MarshalLogObject(enc zapcore.ObjectEncoder) error +// +// var authors []Author = ... +// logger.Info("loading article", zap.Objects("authors", authors)) +// +// Similarly, given a type that implements MarshalLogObject on its pointer +// receiver, you can log a slice of pointers to that object with Objects like +// so: +// +// type Request struct{ ... } +// func (r *Request) MarshalLogObject(enc zapcore.ObjectEncoder) error +// +// var requests []*Request = ... +// logger.Info("sending requests", zap.Objects("requests", requests)) +// +// If instead, you have a slice of values of such an object, use the +// ObjectValues constructor. +// +// var requests []Request = ... +// logger.Info("sending requests", zap.ObjectValues("requests", requests)) +func Objects[T zapcore.ObjectMarshaler](key string, values []T) Field { + return Array(key, objects[T](values)) +} + +type objects[T zapcore.ObjectMarshaler] []T + +func (os objects[T]) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for _, o := range os { + if err := arr.AppendObject(o); err != nil { + return err + } + } + return nil +} + +// ObjectMarshalerPtr is a constraint that specifies that the given type +// implements zapcore.ObjectMarshaler on a pointer receiver. +type ObjectMarshalerPtr[T any] interface { + *T + zapcore.ObjectMarshaler +} + +// ObjectValues constructs a field with the given key, holding a list of the +// provided objects, where pointers to these objects can be marshaled by Zap. +// +// Note that pointers to these objects must implement zapcore.ObjectMarshaler. +// That is, if you're trying to marshal a []Request, the MarshalLogObject +// method must be declared on the *Request type, not the value (Request). +// If it's on the value, use Objects. +// +// Given an object that implements MarshalLogObject on the pointer receiver, +// you can log a slice of those objects with ObjectValues like so: +// +// type Request struct{ ... } +// func (r *Request) MarshalLogObject(enc zapcore.ObjectEncoder) error +// +// var requests []Request = ... +// logger.Info("sending requests", zap.ObjectValues("requests", requests)) +// +// If instead, you have a slice of pointers of such an object, use the Objects +// field constructor. +// +// var requests []*Request = ... +// logger.Info("sending requests", zap.Objects("requests", requests)) +func ObjectValues[T any, P ObjectMarshalerPtr[T]](key string, values []T) Field { + return Array(key, objectValues[T, P](values)) +} + +type objectValues[T any, P ObjectMarshalerPtr[T]] []T + +func (os objectValues[T, P]) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range os { + // It is necessary for us to explicitly reference the "P" type. + // We cannot simply pass "&os[i]" to AppendObject because its type + // is "*T", which the type system does not consider as + // implementing ObjectMarshaler. + // Only the type "P" satisfies ObjectMarshaler, which we have + // to convert "*T" to explicitly. + var p P = &os[i] + if err := arr.AppendObject(p); err != nil { + return err + } + } + return nil +} + +// Strings constructs a field that carries a slice of strings. +func Strings(key string, ss []string) Field { + return Array(key, stringArray(ss)) +} + +// Stringers constructs a field with the given key, holding a list of the +// output provided by the value's String method +// +// Given an object that implements String on the value receiver, you +// can log a slice of those objects with Objects like so: +// +// type Request struct{ ... } +// func (a Request) String() string +// +// var requests []Request = ... +// logger.Info("sending requests", zap.Stringers("requests", requests)) +// +// Note that these objects must implement fmt.Stringer directly. +// That is, if you're trying to marshal a []Request, the String method +// must be declared on the Request type, not its pointer (*Request). +func Stringers[T fmt.Stringer](key string, values []T) Field { + return Array(key, stringers[T](values)) +} + +type stringers[T fmt.Stringer] []T + +func (os stringers[T]) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for _, o := range os { + arr.AppendString(o.String()) + } + return nil +} + +// Times constructs a field that carries a slice of time.Times. +func Times(key string, ts []time.Time) Field { + return Array(key, times(ts)) +} + +// Uints constructs a field that carries a slice of unsigned integers. +func Uints(key string, nums []uint) Field { + return Array(key, uints(nums)) +} + +// Uint64s constructs a field that carries a slice of unsigned integers. +func Uint64s(key string, nums []uint64) Field { + return Array(key, uint64s(nums)) +} + +// Uint32s constructs a field that carries a slice of unsigned integers. +func Uint32s(key string, nums []uint32) Field { + return Array(key, uint32s(nums)) +} + +// Uint16s constructs a field that carries a slice of unsigned integers. +func Uint16s(key string, nums []uint16) Field { + return Array(key, uint16s(nums)) +} + +// Uint8s constructs a field that carries a slice of unsigned integers. +func Uint8s(key string, nums []uint8) Field { + return Array(key, uint8s(nums)) +} + +// Uintptrs constructs a field that carries a slice of pointer addresses. +func Uintptrs(key string, us []uintptr) Field { + return Array(key, uintptrs(us)) +} + +// Errors constructs a field that carries a slice of errors. +func Errors(key string, errs []error) Field { + return Array(key, errArray(errs)) +} + +type bools []bool + +func (bs bools) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range bs { + arr.AppendBool(bs[i]) + } + return nil +} + +type byteStringsArray [][]byte + +func (bss byteStringsArray) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range bss { + arr.AppendByteString(bss[i]) + } + return nil +} + +type complex128s []complex128 + +func (nums complex128s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendComplex128(nums[i]) + } + return nil +} + +type complex64s []complex64 + +func (nums complex64s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendComplex64(nums[i]) + } + return nil +} + +type durations []time.Duration + +func (ds durations) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range ds { + arr.AppendDuration(ds[i]) + } + return nil +} + +type float64s []float64 + +func (nums float64s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendFloat64(nums[i]) + } + return nil +} + +type float32s []float32 + +func (nums float32s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendFloat32(nums[i]) + } + return nil +} + +type ints []int + +func (nums ints) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendInt(nums[i]) + } + return nil +} + +type int64s []int64 + +func (nums int64s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendInt64(nums[i]) + } + return nil +} + +type int32s []int32 + +func (nums int32s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendInt32(nums[i]) + } + return nil +} + +type int16s []int16 + +func (nums int16s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendInt16(nums[i]) + } + return nil +} + +type int8s []int8 + +func (nums int8s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendInt8(nums[i]) + } + return nil +} + +type stringArray []string + +func (ss stringArray) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range ss { + arr.AppendString(ss[i]) + } + return nil +} + +type times []time.Time + +func (ts times) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range ts { + arr.AppendTime(ts[i]) + } + return nil +} + +type uints []uint + +func (nums uints) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUint(nums[i]) + } + return nil +} + +type uint64s []uint64 + +func (nums uint64s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUint64(nums[i]) + } + return nil +} + +type uint32s []uint32 + +func (nums uint32s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUint32(nums[i]) + } + return nil +} + +type uint16s []uint16 + +func (nums uint16s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUint16(nums[i]) + } + return nil +} + +type uint8s []uint8 + +func (nums uint8s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUint8(nums[i]) + } + return nil +} + +type uintptrs []uintptr + +func (nums uintptrs) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUintptr(nums[i]) + } + return nil +} diff --git a/vendor/go.uber.org/zap/array_test.go b/vendor/go.uber.org/zap/array_test.go new file mode 100644 index 0000000000..97738c99ab --- /dev/null +++ b/vendor/go.uber.org/zap/array_test.go @@ -0,0 +1,316 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "fmt" + "testing" + "time" + + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func BenchmarkBoolsArrayMarshaler(b *testing.B) { + // Keep this benchmark here to capture the overhead of the ArrayMarshaler + // wrapper. + bs := make([]bool, 50) + enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{}) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Bools("array", bs).AddTo(enc.Clone()) + } +} + +func BenchmarkBoolsReflect(b *testing.B) { + bs := make([]bool, 50) + enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{}) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Reflect("array", bs).AddTo(enc.Clone()) + } +} + +func TestArrayWrappers(t *testing.T) { + tests := []struct { + desc string + field Field + expected []interface{} + }{ + {"empty bools", Bools("", []bool{}), []interface{}{}}, + {"empty byte strings", ByteStrings("", [][]byte{}), []interface{}{}}, + {"empty complex128s", Complex128s("", []complex128{}), []interface{}{}}, + {"empty complex64s", Complex64s("", []complex64{}), []interface{}{}}, + {"empty durations", Durations("", []time.Duration{}), []interface{}{}}, + {"empty float64s", Float64s("", []float64{}), []interface{}{}}, + {"empty float32s", Float32s("", []float32{}), []interface{}{}}, + {"empty ints", Ints("", []int{}), []interface{}{}}, + {"empty int64s", Int64s("", []int64{}), []interface{}{}}, + {"empty int32s", Int32s("", []int32{}), []interface{}{}}, + {"empty int16s", Int16s("", []int16{}), []interface{}{}}, + {"empty int8s", Int8s("", []int8{}), []interface{}{}}, + {"empty strings", Strings("", []string{}), []interface{}{}}, + {"empty times", Times("", []time.Time{}), []interface{}{}}, + {"empty uints", Uints("", []uint{}), []interface{}{}}, + {"empty uint64s", Uint64s("", []uint64{}), []interface{}{}}, + {"empty uint32s", Uint32s("", []uint32{}), []interface{}{}}, + {"empty uint16s", Uint16s("", []uint16{}), []interface{}{}}, + {"empty uint8s", Uint8s("", []uint8{}), []interface{}{}}, + {"empty uintptrs", Uintptrs("", []uintptr{}), []interface{}{}}, + {"bools", Bools("", []bool{true, false}), []interface{}{true, false}}, + {"byte strings", ByteStrings("", [][]byte{{1, 2}, {3, 4}}), []interface{}{"\x01\x02", "\x03\x04"}}, + {"complex128s", Complex128s("", []complex128{1 + 2i, 3 + 4i}), []interface{}{1 + 2i, 3 + 4i}}, + {"complex64s", Complex64s("", []complex64{1 + 2i, 3 + 4i}), []interface{}{complex64(1 + 2i), complex64(3 + 4i)}}, + {"durations", Durations("", []time.Duration{1, 2}), []interface{}{time.Nanosecond, 2 * time.Nanosecond}}, + {"float64s", Float64s("", []float64{1.2, 3.4}), []interface{}{1.2, 3.4}}, + {"float32s", Float32s("", []float32{1.2, 3.4}), []interface{}{float32(1.2), float32(3.4)}}, + {"ints", Ints("", []int{1, 2}), []interface{}{1, 2}}, + {"int64s", Int64s("", []int64{1, 2}), []interface{}{int64(1), int64(2)}}, + {"int32s", Int32s("", []int32{1, 2}), []interface{}{int32(1), int32(2)}}, + {"int16s", Int16s("", []int16{1, 2}), []interface{}{int16(1), int16(2)}}, + {"int8s", Int8s("", []int8{1, 2}), []interface{}{int8(1), int8(2)}}, + {"strings", Strings("", []string{"foo", "bar"}), []interface{}{"foo", "bar"}}, + {"times", Times("", []time.Time{time.Unix(0, 0), time.Unix(0, 0)}), []interface{}{time.Unix(0, 0), time.Unix(0, 0)}}, + {"uints", Uints("", []uint{1, 2}), []interface{}{uint(1), uint(2)}}, + {"uint64s", Uint64s("", []uint64{1, 2}), []interface{}{uint64(1), uint64(2)}}, + {"uint32s", Uint32s("", []uint32{1, 2}), []interface{}{uint32(1), uint32(2)}}, + {"uint16s", Uint16s("", []uint16{1, 2}), []interface{}{uint16(1), uint16(2)}}, + {"uint8s", Uint8s("", []uint8{1, 2}), []interface{}{uint8(1), uint8(2)}}, + {"uintptrs", Uintptrs("", []uintptr{1, 2}), []interface{}{uintptr(1), uintptr(2)}}, + } + + for _, tt := range tests { + enc := zapcore.NewMapObjectEncoder() + tt.field.Key = "k" + tt.field.AddTo(enc) + assert.Equal(t, tt.expected, enc.Fields["k"], "%s: unexpected map contents.", tt.desc) + assert.Equal(t, 1, len(enc.Fields), "%s: found extra keys in map: %v", tt.desc, enc.Fields) + } +} + +func TestObjectsAndObjectValues(t *testing.T) { + t.Parallel() + + tests := []struct { + desc string + give Field + want []any + }{ + { + desc: "Objects/nil slice", + give: Objects[*emptyObject]("", nil), + want: []any{}, + }, + { + desc: "ObjectValues/nil slice", + give: ObjectValues[emptyObject]("", nil), + want: []any{}, + }, + { + desc: "ObjectValues/empty slice", + give: ObjectValues("", []emptyObject{}), + want: []any{}, + }, + { + desc: "ObjectValues/single item", + give: ObjectValues("", []emptyObject{ + {}, + }), + want: []any{ + map[string]any{}, + }, + }, + { + desc: "Objects/multiple different objects", + give: Objects("", []*fakeObject{ + {value: "foo"}, + {value: "bar"}, + {value: "baz"}, + }), + want: []any{ + map[string]any{"value": "foo"}, + map[string]any{"value": "bar"}, + map[string]any{"value": "baz"}, + }, + }, + { + desc: "ObjectValues/multiple different objects", + give: ObjectValues("", []fakeObject{ + {value: "foo"}, + {value: "bar"}, + {value: "baz"}, + }), + want: []any{ + map[string]any{"value": "foo"}, + map[string]any{"value": "bar"}, + map[string]any{"value": "baz"}, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + tt.give.Key = "k" + + enc := zapcore.NewMapObjectEncoder() + tt.give.AddTo(enc) + assert.Equal(t, tt.want, enc.Fields["k"]) + }) + } +} + +type emptyObject struct{} + +func (*emptyObject) MarshalLogObject(zapcore.ObjectEncoder) error { + return nil +} + +type fakeObject struct { + value string + err error // marshaling error, if any +} + +func (o *fakeObject) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("value", o.value) + return o.err +} + +func TestObjectsAndObjectValues_marshalError(t *testing.T) { + t.Parallel() + + tests := []struct { + desc string + give Field + want []any + wantErr string + }{ + { + desc: "Objects", + give: Objects("", []*fakeObject{ + {value: "foo"}, + {value: "bar", err: errors.New("great sadness")}, + {value: "baz"}, // does not get marshaled + }), + want: []any{ + map[string]any{"value": "foo"}, + map[string]any{"value": "bar"}, + }, + wantErr: "great sadness", + }, + { + desc: "ObjectValues", + give: ObjectValues("", []fakeObject{ + {value: "foo"}, + {value: "bar", err: errors.New("stuff failed")}, + {value: "baz"}, // does not get marshaled + }), + want: []any{ + map[string]any{"value": "foo"}, + map[string]any{"value": "bar"}, + }, + wantErr: "stuff failed", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + tt.give.Key = "k" + + enc := zapcore.NewMapObjectEncoder() + tt.give.AddTo(enc) + + require.Contains(t, enc.Fields, "k") + assert.Equal(t, tt.want, enc.Fields["k"]) + + // AddTo puts the error in a "%vError" field based on the name of the + // original field. + require.Contains(t, enc.Fields, "kError") + assert.Equal(t, tt.wantErr, enc.Fields["kError"]) + }) + } +} + +type stringerObject struct { + value string +} + +func (s stringerObject) String() string { + return s.value +} + +func TestStringers(t *testing.T) { + t.Parallel() + + tests := []struct { + desc string + give Field + want []any + }{ + { + desc: "Stringers", + give: Stringers("", []stringerObject{ + {value: "foo"}, + {value: "bar"}, + {value: "baz"}, + }), + want: []any{ + "foo", + "bar", + "baz", + }, + }, + { + desc: "Stringers with []fmt.Stringer", + give: Stringers("", []fmt.Stringer{ + stringerObject{value: "foo"}, + stringerObject{value: "bar"}, + stringerObject{value: "baz"}, + }), + want: []any{ + "foo", + "bar", + "baz", + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + tt.give.Key = "k" + + enc := zapcore.NewMapObjectEncoder() + tt.give.AddTo(enc) + assert.Equal(t, tt.want, enc.Fields["k"]) + }) + } +} diff --git a/vendor/go.uber.org/zap/buffer/buffer.go b/vendor/go.uber.org/zap/buffer/buffer.go new file mode 100644 index 0000000000..27fb5cd5da --- /dev/null +++ b/vendor/go.uber.org/zap/buffer/buffer.go @@ -0,0 +1,146 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package buffer provides a thin wrapper around a byte slice. Unlike the +// standard library's bytes.Buffer, it supports a portion of the strconv +// package's zero-allocation formatters. +package buffer // import "go.uber.org/zap/buffer" + +import ( + "strconv" + "time" +) + +const _size = 1024 // by default, create 1 KiB buffers + +// Buffer is a thin wrapper around a byte slice. It's intended to be pooled, so +// the only way to construct one is via a Pool. +type Buffer struct { + bs []byte + pool Pool +} + +// AppendByte writes a single byte to the Buffer. +func (b *Buffer) AppendByte(v byte) { + b.bs = append(b.bs, v) +} + +// AppendBytes writes a single byte to the Buffer. +func (b *Buffer) AppendBytes(v []byte) { + b.bs = append(b.bs, v...) +} + +// AppendString writes a string to the Buffer. +func (b *Buffer) AppendString(s string) { + b.bs = append(b.bs, s...) +} + +// AppendInt appends an integer to the underlying buffer (assuming base 10). +func (b *Buffer) AppendInt(i int64) { + b.bs = strconv.AppendInt(b.bs, i, 10) +} + +// AppendTime appends the time formatted using the specified layout. +func (b *Buffer) AppendTime(t time.Time, layout string) { + b.bs = t.AppendFormat(b.bs, layout) +} + +// AppendUint appends an unsigned integer to the underlying buffer (assuming +// base 10). +func (b *Buffer) AppendUint(i uint64) { + b.bs = strconv.AppendUint(b.bs, i, 10) +} + +// AppendBool appends a bool to the underlying buffer. +func (b *Buffer) AppendBool(v bool) { + b.bs = strconv.AppendBool(b.bs, v) +} + +// AppendFloat appends a float to the underlying buffer. It doesn't quote NaN +// or +/- Inf. +func (b *Buffer) AppendFloat(f float64, bitSize int) { + b.bs = strconv.AppendFloat(b.bs, f, 'f', -1, bitSize) +} + +// Len returns the length of the underlying byte slice. +func (b *Buffer) Len() int { + return len(b.bs) +} + +// Cap returns the capacity of the underlying byte slice. +func (b *Buffer) Cap() int { + return cap(b.bs) +} + +// Bytes returns a mutable reference to the underlying byte slice. +func (b *Buffer) Bytes() []byte { + return b.bs +} + +// String returns a string copy of the underlying byte slice. +func (b *Buffer) String() string { + return string(b.bs) +} + +// Reset resets the underlying byte slice. Subsequent writes re-use the slice's +// backing array. +func (b *Buffer) Reset() { + b.bs = b.bs[:0] +} + +// Write implements io.Writer. +func (b *Buffer) Write(bs []byte) (int, error) { + b.bs = append(b.bs, bs...) + return len(bs), nil +} + +// WriteByte writes a single byte to the Buffer. +// +// Error returned is always nil, function signature is compatible +// with bytes.Buffer and bufio.Writer +func (b *Buffer) WriteByte(v byte) error { + b.AppendByte(v) + return nil +} + +// WriteString writes a string to the Buffer. +// +// Error returned is always nil, function signature is compatible +// with bytes.Buffer and bufio.Writer +func (b *Buffer) WriteString(s string) (int, error) { + b.AppendString(s) + return len(s), nil +} + +// TrimNewline trims any final "\n" byte from the end of the buffer. +func (b *Buffer) TrimNewline() { + if i := len(b.bs) - 1; i >= 0 { + if b.bs[i] == '\n' { + b.bs = b.bs[:i] + } + } +} + +// Free returns the Buffer to its Pool. +// +// Callers must not retain references to the Buffer after calling Free. +func (b *Buffer) Free() { + b.pool.put(b) +} diff --git a/vendor/go.uber.org/zap/buffer/buffer_test.go b/vendor/go.uber.org/zap/buffer/buffer_test.go new file mode 100644 index 0000000000..71ffac1146 --- /dev/null +++ b/vendor/go.uber.org/zap/buffer/buffer_test.go @@ -0,0 +1,95 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package buffer + +import ( + "bytes" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestBufferWrites(t *testing.T) { + buf := NewPool().Get() + + tests := []struct { + desc string + f func() + want string + }{ + {"AppendByte", func() { buf.AppendByte('v') }, "v"}, + {"AppendString", func() { buf.AppendString("foo") }, "foo"}, + {"AppendIntPositive", func() { buf.AppendInt(42) }, "42"}, + {"AppendIntNegative", func() { buf.AppendInt(-42) }, "-42"}, + {"AppendUint", func() { buf.AppendUint(42) }, "42"}, + {"AppendBool", func() { buf.AppendBool(true) }, "true"}, + {"AppendFloat64", func() { buf.AppendFloat(3.14, 64) }, "3.14"}, + // Intentionally introduce some floating-point error. + {"AppendFloat32", func() { buf.AppendFloat(float64(float32(3.14)), 32) }, "3.14"}, + {"AppendWrite", func() { buf.Write([]byte("foo")) }, "foo"}, + {"AppendTime", func() { buf.AppendTime(time.Date(2000, 1, 2, 3, 4, 5, 6, time.UTC), time.RFC3339) }, "2000-01-02T03:04:05Z"}, + {"WriteByte", func() { buf.WriteByte('v') }, "v"}, + {"WriteString", func() { buf.WriteString("foo") }, "foo"}, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + buf.Reset() + tt.f() + assert.Equal(t, tt.want, buf.String(), "Unexpected buffer.String().") + assert.Equal(t, tt.want, string(buf.Bytes()), "Unexpected string(buffer.Bytes()).") + assert.Equal(t, len(tt.want), buf.Len(), "Unexpected buffer length.") + // We're not writing more than a kibibyte in tests. + assert.Equal(t, _size, buf.Cap(), "Expected buffer capacity to remain constant.") + }) + } +} + +func BenchmarkBuffers(b *testing.B) { + // Because we use the strconv.AppendFoo functions so liberally, we can't + // use the standard library's bytes.Buffer anyways (without incurring a + // bunch of extra allocations). Nevertheless, let's make sure that we're + // not losing any precious nanoseconds. + str := strings.Repeat("a", 1024) + slice := make([]byte, 1024) + buf := bytes.NewBuffer(slice) + custom := NewPool().Get() + b.Run("ByteSlice", func(b *testing.B) { + for i := 0; i < b.N; i++ { + slice = append(slice, str...) + slice = slice[:0] + } + }) + b.Run("BytesBuffer", func(b *testing.B) { + for i := 0; i < b.N; i++ { + buf.WriteString(str) + buf.Reset() + } + }) + b.Run("CustomBuffer", func(b *testing.B) { + for i := 0; i < b.N; i++ { + custom.AppendString(str) + custom.Reset() + } + }) +} diff --git a/vendor/go.uber.org/zap/buffer/gotest/ya.make b/vendor/go.uber.org/zap/buffer/gotest/ya.make new file mode 100644 index 0000000000..974b099945 --- /dev/null +++ b/vendor/go.uber.org/zap/buffer/gotest/ya.make @@ -0,0 +1,5 @@ +GO_TEST_FOR(vendor/go.uber.org/zap/buffer) + +LICENSE(MIT) + +END() diff --git a/vendor/go.uber.org/zap/buffer/pool.go b/vendor/go.uber.org/zap/buffer/pool.go new file mode 100644 index 0000000000..846323360e --- /dev/null +++ b/vendor/go.uber.org/zap/buffer/pool.go @@ -0,0 +1,53 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package buffer + +import ( + "go.uber.org/zap/internal/pool" +) + +// A Pool is a type-safe wrapper around a sync.Pool. +type Pool struct { + p *pool.Pool[*Buffer] +} + +// NewPool constructs a new Pool. +func NewPool() Pool { + return Pool{ + p: pool.New(func() *Buffer { + return &Buffer{ + bs: make([]byte, 0, _size), + } + }), + } +} + +// Get retrieves a Buffer from the pool, creating one if necessary. +func (p Pool) Get() *Buffer { + buf := p.p.Get() + buf.Reset() + buf.pool = p + return buf +} + +func (p Pool) put(buf *Buffer) { + p.p.Put(buf) +} diff --git a/vendor/go.uber.org/zap/buffer/pool_test.go b/vendor/go.uber.org/zap/buffer/pool_test.go new file mode 100644 index 0000000000..a219815b55 --- /dev/null +++ b/vendor/go.uber.org/zap/buffer/pool_test.go @@ -0,0 +1,52 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package buffer + +import ( + "sync" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBuffers(t *testing.T) { + const dummyData = "dummy data" + p := NewPool() + + var wg sync.WaitGroup + for g := 0; g < 10; g++ { + wg.Add(1) + go func() { + for i := 0; i < 100; i++ { + buf := p.Get() + assert.Zero(t, buf.Len(), "Expected truncated buffer") + assert.NotZero(t, buf.Cap(), "Expected non-zero capacity") + + buf.AppendString(dummyData) + assert.Equal(t, buf.Len(), len(dummyData), "Expected buffer to contain dummy data") + + buf.Free() + } + wg.Done() + }() + } + wg.Wait() +} diff --git a/vendor/go.uber.org/zap/buffer/ya.make b/vendor/go.uber.org/zap/buffer/ya.make new file mode 100644 index 0000000000..f4c893dc92 --- /dev/null +++ b/vendor/go.uber.org/zap/buffer/ya.make @@ -0,0 +1,19 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + buffer.go + pool.go +) + +GO_TEST_SRCS( + buffer_test.go + pool_test.go +) + +END() + +RECURSE( + gotest +) diff --git a/vendor/go.uber.org/zap/clock_test.go b/vendor/go.uber.org/zap/clock_test.go new file mode 100644 index 0000000000..29825fc2fb --- /dev/null +++ b/vendor/go.uber.org/zap/clock_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest/observer" +) + +type constantClock time.Time + +func (c constantClock) Now() time.Time { return time.Time(c) } +func (c constantClock) NewTicker(d time.Duration) *time.Ticker { + return &time.Ticker{} +} + +func TestWithClock(t *testing.T) { + date := time.Date(2077, 1, 23, 10, 15, 13, 441, time.UTC) + clock := constantClock(date) + withLogger(t, DebugLevel, []Option{WithClock(clock)}, func(log *Logger, logs *observer.ObservedLogs) { + log.Info("") + require.Equal(t, 1, logs.Len(), "Expected only one log entry to be written.") + assert.Equal(t, date, logs.All()[0].Time, "Unexpected entry time.") + }) +} diff --git a/vendor/go.uber.org/zap/common_test.go b/vendor/go.uber.org/zap/common_test.go new file mode 100644 index 0000000000..b0a4a2e520 --- /dev/null +++ b/vendor/go.uber.org/zap/common_test.go @@ -0,0 +1,57 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "sync" + "testing" + + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" +) + +func opts(opts ...Option) []Option { + return opts +} + +// Here specifically to introduce an easily-identifiable filename for testing +// stacktraces and caller skips. +func withLogger(t testing.TB, e zapcore.LevelEnabler, opts []Option, f func(*Logger, *observer.ObservedLogs)) { + fac, logs := observer.New(e) + log := New(fac, opts...) + f(log, logs) +} + +func withSugar(t testing.TB, e zapcore.LevelEnabler, opts []Option, f func(*SugaredLogger, *observer.ObservedLogs)) { + withLogger(t, e, opts, func(logger *Logger, logs *observer.ObservedLogs) { f(logger.Sugar(), logs) }) +} + +func runConcurrently(goroutines, iterations int, wg *sync.WaitGroup, f func()) { + wg.Add(goroutines) + for g := 0; g < goroutines; g++ { + go func() { + defer wg.Done() + for i := 0; i < iterations; i++ { + f() + } + }() + } +} diff --git a/vendor/go.uber.org/zap/config.go b/vendor/go.uber.org/zap/config.go new file mode 100644 index 0000000000..e76e4e64fb --- /dev/null +++ b/vendor/go.uber.org/zap/config.go @@ -0,0 +1,330 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "sort" + "time" + + "go.uber.org/zap/zapcore" +) + +// SamplingConfig sets a sampling strategy for the logger. Sampling caps the +// global CPU and I/O load that logging puts on your process while attempting +// to preserve a representative subset of your logs. +// +// If specified, the Sampler will invoke the Hook after each decision. +// +// Values configured here are per-second. See zapcore.NewSamplerWithOptions for +// details. +type SamplingConfig struct { + Initial int `json:"initial" yaml:"initial"` + Thereafter int `json:"thereafter" yaml:"thereafter"` + Hook func(zapcore.Entry, zapcore.SamplingDecision) `json:"-" yaml:"-"` +} + +// Config offers a declarative way to construct a logger. It doesn't do +// anything that can't be done with New, Options, and the various +// zapcore.WriteSyncer and zapcore.Core wrappers, but it's a simpler way to +// toggle common options. +// +// Note that Config intentionally supports only the most common options. More +// unusual logging setups (logging to network connections or message queues, +// splitting output between multiple files, etc.) are possible, but require +// direct use of the zapcore package. For sample code, see the package-level +// BasicConfiguration and AdvancedConfiguration examples. +// +// For an example showing runtime log level changes, see the documentation for +// AtomicLevel. +type Config struct { + // Level is the minimum enabled logging level. Note that this is a dynamic + // level, so calling Config.Level.SetLevel will atomically change the log + // level of all loggers descended from this config. + Level AtomicLevel `json:"level" yaml:"level"` + // Development puts the logger in development mode, which changes the + // behavior of DPanicLevel and takes stacktraces more liberally. + Development bool `json:"development" yaml:"development"` + // DisableCaller stops annotating logs with the calling function's file + // name and line number. By default, all logs are annotated. + DisableCaller bool `json:"disableCaller" yaml:"disableCaller"` + // DisableStacktrace completely disables automatic stacktrace capturing. By + // default, stacktraces are captured for WarnLevel and above logs in + // development and ErrorLevel and above in production. + DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"` + // Sampling sets a sampling policy. A nil SamplingConfig disables sampling. + Sampling *SamplingConfig `json:"sampling" yaml:"sampling"` + // Encoding sets the logger's encoding. Valid values are "json" and + // "console", as well as any third-party encodings registered via + // RegisterEncoder. + Encoding string `json:"encoding" yaml:"encoding"` + // EncoderConfig sets options for the chosen encoder. See + // zapcore.EncoderConfig for details. + EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"` + // OutputPaths is a list of URLs or file paths to write logging output to. + // See Open for details. + OutputPaths []string `json:"outputPaths" yaml:"outputPaths"` + // ErrorOutputPaths is a list of URLs to write internal logger errors to. + // The default is standard error. + // + // Note that this setting only affects internal errors; for sample code that + // sends error-level logs to a different location from info- and debug-level + // logs, see the package-level AdvancedConfiguration example. + ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"` + // InitialFields is a collection of fields to add to the root logger. + InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"` +} + +// NewProductionEncoderConfig returns an opinionated EncoderConfig for +// production environments. +// +// Messages encoded with this configuration will be JSON-formatted +// and will have the following keys by default: +// +// - "level": The logging level (e.g. "info", "error"). +// - "ts": The current time in number of seconds since the Unix epoch. +// - "msg": The message passed to the log statement. +// - "caller": If available, a short path to the file and line number +// where the log statement was issued. +// The logger configuration determines whether this field is captured. +// - "stacktrace": If available, a stack trace from the line +// where the log statement was issued. +// The logger configuration determines whether this field is captured. +// +// By default, the following formats are used for different types: +// +// - Time is formatted as floating-point number of seconds since the Unix +// epoch. +// - Duration is formatted as floating-point number of seconds. +// +// You may change these by setting the appropriate fields in the returned +// object. +// For example, use the following to change the time encoding format: +// +// cfg := zap.NewProductionEncoderConfig() +// cfg.EncodeTime = zapcore.ISO8601TimeEncoder +func NewProductionEncoderConfig() zapcore.EncoderConfig { + return zapcore.EncoderConfig{ + TimeKey: "ts", + LevelKey: "level", + NameKey: "logger", + CallerKey: "caller", + FunctionKey: zapcore.OmitKey, + MessageKey: "msg", + StacktraceKey: "stacktrace", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.EpochTimeEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + } +} + +// NewProductionConfig builds a reasonable default production logging +// configuration. +// Logging is enabled at InfoLevel and above, and uses a JSON encoder. +// Logs are written to standard error. +// Stacktraces are included on logs of ErrorLevel and above. +// DPanicLevel logs will not panic, but will write a stacktrace. +// +// Sampling is enabled at 100:100 by default, +// meaning that after the first 100 log entries +// with the same level and message in the same second, +// it will log every 100th entry +// with the same level and message in the same second. +// You may disable this behavior by setting Sampling to nil. +// +// See [NewProductionEncoderConfig] for information +// on the default encoder configuration. +func NewProductionConfig() Config { + return Config{ + Level: NewAtomicLevelAt(InfoLevel), + Development: false, + Sampling: &SamplingConfig{ + Initial: 100, + Thereafter: 100, + }, + Encoding: "json", + EncoderConfig: NewProductionEncoderConfig(), + OutputPaths: []string{"stderr"}, + ErrorOutputPaths: []string{"stderr"}, + } +} + +// NewDevelopmentEncoderConfig returns an opinionated EncoderConfig for +// development environments. +// +// Messages encoded with this configuration will use Zap's console encoder +// intended to print human-readable output. +// It will print log messages with the following information: +// +// - The log level (e.g. "INFO", "ERROR"). +// - The time in ISO8601 format (e.g. "2017-01-01T12:00:00Z"). +// - The message passed to the log statement. +// - If available, a short path to the file and line number +// where the log statement was issued. +// The logger configuration determines whether this field is captured. +// - If available, a stacktrace from the line +// where the log statement was issued. +// The logger configuration determines whether this field is captured. +// +// By default, the following formats are used for different types: +// +// - Time is formatted in ISO8601 format (e.g. "2017-01-01T12:00:00Z"). +// - Duration is formatted as a string (e.g. "1.234s"). +// +// You may change these by setting the appropriate fields in the returned +// object. +// For example, use the following to change the time encoding format: +// +// cfg := zap.NewDevelopmentEncoderConfig() +// cfg.EncodeTime = zapcore.ISO8601TimeEncoder +func NewDevelopmentEncoderConfig() zapcore.EncoderConfig { + return zapcore.EncoderConfig{ + // Keys can be anything except the empty string. + TimeKey: "T", + LevelKey: "L", + NameKey: "N", + CallerKey: "C", + FunctionKey: zapcore.OmitKey, + MessageKey: "M", + StacktraceKey: "S", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.CapitalLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + } +} + +// NewDevelopmentConfig builds a reasonable default development logging +// configuration. +// Logging is enabled at DebugLevel and above, and uses a console encoder. +// Logs are written to standard error. +// Stacktraces are included on logs of WarnLevel and above. +// DPanicLevel logs will panic. +// +// See [NewDevelopmentEncoderConfig] for information +// on the default encoder configuration. +func NewDevelopmentConfig() Config { + return Config{ + Level: NewAtomicLevelAt(DebugLevel), + Development: true, + Encoding: "console", + EncoderConfig: NewDevelopmentEncoderConfig(), + OutputPaths: []string{"stderr"}, + ErrorOutputPaths: []string{"stderr"}, + } +} + +// Build constructs a logger from the Config and Options. +func (cfg Config) Build(opts ...Option) (*Logger, error) { + enc, err := cfg.buildEncoder() + if err != nil { + return nil, err + } + + sink, errSink, err := cfg.openSinks() + if err != nil { + return nil, err + } + + if cfg.Level == (AtomicLevel{}) { + return nil, errors.New("missing Level") + } + + log := New( + zapcore.NewCore(enc, sink, cfg.Level), + cfg.buildOptions(errSink)..., + ) + if len(opts) > 0 { + log = log.WithOptions(opts...) + } + return log, nil +} + +func (cfg Config) buildOptions(errSink zapcore.WriteSyncer) []Option { + opts := []Option{ErrorOutput(errSink)} + + if cfg.Development { + opts = append(opts, Development()) + } + + if !cfg.DisableCaller { + opts = append(opts, AddCaller()) + } + + stackLevel := ErrorLevel + if cfg.Development { + stackLevel = WarnLevel + } + if !cfg.DisableStacktrace { + opts = append(opts, AddStacktrace(stackLevel)) + } + + if scfg := cfg.Sampling; scfg != nil { + opts = append(opts, WrapCore(func(core zapcore.Core) zapcore.Core { + var samplerOpts []zapcore.SamplerOption + if scfg.Hook != nil { + samplerOpts = append(samplerOpts, zapcore.SamplerHook(scfg.Hook)) + } + return zapcore.NewSamplerWithOptions( + core, + time.Second, + cfg.Sampling.Initial, + cfg.Sampling.Thereafter, + samplerOpts..., + ) + })) + } + + if len(cfg.InitialFields) > 0 { + fs := make([]Field, 0, len(cfg.InitialFields)) + keys := make([]string, 0, len(cfg.InitialFields)) + for k := range cfg.InitialFields { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + fs = append(fs, Any(k, cfg.InitialFields[k])) + } + opts = append(opts, Fields(fs...)) + } + + return opts +} + +func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) { + sink, closeOut, err := Open(cfg.OutputPaths...) + if err != nil { + return nil, nil, err + } + errSink, _, err := Open(cfg.ErrorOutputPaths...) + if err != nil { + closeOut() + return nil, nil, err + } + return sink, errSink, nil +} + +func (cfg Config) buildEncoder() (zapcore.Encoder, error) { + return newEncoder(cfg.Encoding, cfg.EncoderConfig) +} diff --git a/vendor/go.uber.org/zap/config_test.go b/vendor/go.uber.org/zap/config_test.go new file mode 100644 index 0000000000..4badd1beec --- /dev/null +++ b/vendor/go.uber.org/zap/config_test.go @@ -0,0 +1,203 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "os" + "path/filepath" + "sync/atomic" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" +) + +func TestConfig(t *testing.T) { + tests := []struct { + desc string + cfg Config + expectN int64 + expectRe string + }{ + { + desc: "production", + cfg: NewProductionConfig(), + expectN: 2 + 100 + 1, // 2 from initial logs, 100 initial sampled logs, 1 from off-by-one in sampler + expectRe: `{"level":"info","caller":"[a-z0-9_-]+/config_test.go:\d+","msg":"info","k":"v","z":"zz"}` + "\n" + + `{"level":"warn","caller":"[a-z0-9_-]+/config_test.go:\d+","msg":"warn","k":"v","z":"zz"}` + "\n", + }, + { + desc: "development", + cfg: NewDevelopmentConfig(), + expectN: 3 + 200, // 3 initial logs, all 200 subsequent logs + expectRe: "DEBUG\t[a-z0-9_-]+/config_test.go:" + `\d+` + "\tdebug\t" + `{"k": "v", "z": "zz"}` + "\n" + + "INFO\t[a-z0-9_-]+/config_test.go:" + `\d+` + "\tinfo\t" + `{"k": "v", "z": "zz"}` + "\n" + + "WARN\t[a-z0-9_-]+/config_test.go:" + `\d+` + "\twarn\t" + `{"k": "v", "z": "zz"}` + "\n" + + `go.uber.org/zap.TestConfig.\w+`, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + logOut := filepath.Join(t.TempDir(), "test.log") + + tt.cfg.OutputPaths = []string{logOut} + tt.cfg.EncoderConfig.TimeKey = "" // no timestamps in tests + tt.cfg.InitialFields = map[string]interface{}{"z": "zz", "k": "v"} + + hook, count := makeCountingHook() + logger, err := tt.cfg.Build(Hooks(hook)) + require.NoError(t, err, "Unexpected error constructing logger.") + + logger.Debug("debug") + logger.Info("info") + logger.Warn("warn") + + byteContents, err := os.ReadFile(logOut) + require.NoError(t, err, "Couldn't read log contents from temp file.") + logs := string(byteContents) + assert.Regexp(t, tt.expectRe, logs, "Unexpected log output.") + + for i := 0; i < 200; i++ { + logger.Info("sampling") + } + assert.Equal(t, tt.expectN, count.Load(), "Hook called an unexpected number of times.") + }) + } +} + +func TestConfigWithInvalidPaths(t *testing.T) { + tests := []struct { + desc string + output string + errOutput string + }{ + {"output directory doesn't exist", "/tmp/not-there/foo.log", "stderr"}, + {"error output directory doesn't exist", "stdout", "/tmp/not-there/foo-errors.log"}, + {"neither output directory exists", "/tmp/not-there/foo.log", "/tmp/not-there/foo-errors.log"}, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + cfg := NewProductionConfig() + cfg.OutputPaths = []string{tt.output} + cfg.ErrorOutputPaths = []string{tt.errOutput} + _, err := cfg.Build() + assert.Error(t, err, "Expected an error opening a non-existent directory.") + }) + } +} + +func TestConfigWithMissingAttributes(t *testing.T) { + tests := []struct { + desc string + cfg Config + expectErr string + }{ + { + desc: "missing level", + cfg: Config{ + Encoding: "json", + }, + expectErr: "missing Level", + }, + { + desc: "missing encoder time in encoder config", + cfg: Config{ + Level: NewAtomicLevelAt(zapcore.InfoLevel), + Encoding: "json", + EncoderConfig: zapcore.EncoderConfig{ + MessageKey: "msg", + TimeKey: "ts", + }, + }, + expectErr: "missing EncodeTime in EncoderConfig", + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + cfg := tt.cfg + _, err := cfg.Build() + assert.EqualError(t, err, tt.expectErr) + }) + } +} + +func makeSamplerCountingHook() (h func(zapcore.Entry, zapcore.SamplingDecision), + dropped, sampled *atomic.Int64, +) { + dropped = new(atomic.Int64) + sampled = new(atomic.Int64) + h = func(_ zapcore.Entry, dec zapcore.SamplingDecision) { + if dec&zapcore.LogDropped > 0 { + dropped.Add(1) + } else if dec&zapcore.LogSampled > 0 { + sampled.Add(1) + } + } + return h, dropped, sampled +} + +func TestConfigWithSamplingHook(t *testing.T) { + shook, dcount, scount := makeSamplerCountingHook() + cfg := Config{ + Level: NewAtomicLevelAt(InfoLevel), + Development: false, + Sampling: &SamplingConfig{ + Initial: 100, + Thereafter: 100, + Hook: shook, + }, + Encoding: "json", + EncoderConfig: NewProductionEncoderConfig(), + OutputPaths: []string{"stderr"}, + ErrorOutputPaths: []string{"stderr"}, + } + expectRe := `{"level":"info","caller":"[a-z0-9_-]+/config_test.go:\d+","msg":"info","k":"v","z":"zz"}` + "\n" + + `{"level":"warn","caller":"[a-z0-9_-]+/config_test.go:\d+","msg":"warn","k":"v","z":"zz"}` + "\n" + expectDropped := 99 // 200 - 100 initial - 1 thereafter + expectSampled := 103 // 2 from initial + 100 + 1 thereafter + + logOut := filepath.Join(t.TempDir(), "test.log") + cfg.OutputPaths = []string{logOut} + cfg.EncoderConfig.TimeKey = "" // no timestamps in tests + cfg.InitialFields = map[string]interface{}{"z": "zz", "k": "v"} + + logger, err := cfg.Build() + require.NoError(t, err, "Unexpected error constructing logger.") + + logger.Debug("debug") + logger.Info("info") + logger.Warn("warn") + + byteContents, err := os.ReadFile(logOut) + require.NoError(t, err, "Couldn't read log contents from temp file.") + logs := string(byteContents) + assert.Regexp(t, expectRe, logs, "Unexpected log output.") + + for i := 0; i < 200; i++ { + logger.Info("sampling") + } + assert.Equal(t, int64(expectDropped), dcount.Load()) + assert.Equal(t, int64(expectSampled), scount.Load()) +} diff --git a/vendor/go.uber.org/zap/doc.go b/vendor/go.uber.org/zap/doc.go new file mode 100644 index 0000000000..3c50d7b4d3 --- /dev/null +++ b/vendor/go.uber.org/zap/doc.go @@ -0,0 +1,117 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package zap provides fast, structured, leveled logging. +// +// For applications that log in the hot path, reflection-based serialization +// and string formatting are prohibitively expensive - they're CPU-intensive +// and make many small allocations. Put differently, using json.Marshal and +// fmt.Fprintf to log tons of interface{} makes your application slow. +// +// Zap takes a different approach. It includes a reflection-free, +// zero-allocation JSON encoder, and the base Logger strives to avoid +// serialization overhead and allocations wherever possible. By building the +// high-level SugaredLogger on that foundation, zap lets users choose when +// they need to count every allocation and when they'd prefer a more familiar, +// loosely typed API. +// +// # Choosing a Logger +// +// In contexts where performance is nice, but not critical, use the +// SugaredLogger. It's 4-10x faster than other structured logging packages and +// supports both structured and printf-style logging. Like log15 and go-kit, +// the SugaredLogger's structured logging APIs are loosely typed and accept a +// variadic number of key-value pairs. (For more advanced use cases, they also +// accept strongly typed fields - see the SugaredLogger.With documentation for +// details.) +// +// sugar := zap.NewExample().Sugar() +// defer sugar.Sync() +// sugar.Infow("failed to fetch URL", +// "url", "http://example.com", +// "attempt", 3, +// "backoff", time.Second, +// ) +// sugar.Infof("failed to fetch URL: %s", "http://example.com") +// +// By default, loggers are unbuffered. However, since zap's low-level APIs +// allow buffering, calling Sync before letting your process exit is a good +// habit. +// +// In the rare contexts where every microsecond and every allocation matter, +// use the Logger. It's even faster than the SugaredLogger and allocates far +// less, but it only supports strongly-typed, structured logging. +// +// logger := zap.NewExample() +// defer logger.Sync() +// logger.Info("failed to fetch URL", +// zap.String("url", "http://example.com"), +// zap.Int("attempt", 3), +// zap.Duration("backoff", time.Second), +// ) +// +// Choosing between the Logger and SugaredLogger doesn't need to be an +// application-wide decision: converting between the two is simple and +// inexpensive. +// +// logger := zap.NewExample() +// defer logger.Sync() +// sugar := logger.Sugar() +// plain := sugar.Desugar() +// +// # Configuring Zap +// +// The simplest way to build a Logger is to use zap's opinionated presets: +// NewExample, NewProduction, and NewDevelopment. These presets build a logger +// with a single function call: +// +// logger, err := zap.NewProduction() +// if err != nil { +// log.Fatalf("can't initialize zap logger: %v", err) +// } +// defer logger.Sync() +// +// Presets are fine for small projects, but larger projects and organizations +// naturally require a bit more customization. For most users, zap's Config +// struct strikes the right balance between flexibility and convenience. See +// the package-level BasicConfiguration example for sample code. +// +// More unusual configurations (splitting output between files, sending logs +// to a message queue, etc.) are possible, but require direct use of +// go.uber.org/zap/zapcore. See the package-level AdvancedConfiguration +// example for sample code. +// +// # Extending Zap +// +// The zap package itself is a relatively thin wrapper around the interfaces +// in go.uber.org/zap/zapcore. Extending zap to support a new encoding (e.g., +// BSON), a new log sink (e.g., Kafka), or something more exotic (perhaps an +// exception aggregation service, like Sentry or Rollbar) typically requires +// implementing the zapcore.Encoder, zapcore.WriteSyncer, or zapcore.Core +// interfaces. See the zapcore documentation for details. +// +// Similarly, package authors can use the high-performance Encoder and Core +// implementations in the zapcore package to build their own loggers. +// +// # Frequently Asked Questions +// +// An FAQ covering everything from installation errors to design decisions is +// available at https://github.com/uber-go/zap/blob/master/FAQ.md. +package zap // import "go.uber.org/zap" diff --git a/vendor/go.uber.org/zap/encoder.go b/vendor/go.uber.org/zap/encoder.go new file mode 100644 index 0000000000..caa04ceefd --- /dev/null +++ b/vendor/go.uber.org/zap/encoder.go @@ -0,0 +1,79 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "fmt" + "sync" + + "go.uber.org/zap/zapcore" +) + +var ( + errNoEncoderNameSpecified = errors.New("no encoder name specified") + + _encoderNameToConstructor = map[string]func(zapcore.EncoderConfig) (zapcore.Encoder, error){ + "console": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { + return zapcore.NewConsoleEncoder(encoderConfig), nil + }, + "json": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { + return zapcore.NewJSONEncoder(encoderConfig), nil + }, + } + _encoderMutex sync.RWMutex +) + +// RegisterEncoder registers an encoder constructor, which the Config struct +// can then reference. By default, the "json" and "console" encoders are +// registered. +// +// Attempting to register an encoder whose name is already taken returns an +// error. +func RegisterEncoder(name string, constructor func(zapcore.EncoderConfig) (zapcore.Encoder, error)) error { + _encoderMutex.Lock() + defer _encoderMutex.Unlock() + if name == "" { + return errNoEncoderNameSpecified + } + if _, ok := _encoderNameToConstructor[name]; ok { + return fmt.Errorf("encoder already registered for name %q", name) + } + _encoderNameToConstructor[name] = constructor + return nil +} + +func newEncoder(name string, encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { + if encoderConfig.TimeKey != "" && encoderConfig.EncodeTime == nil { + return nil, errors.New("missing EncodeTime in EncoderConfig") + } + + _encoderMutex.RLock() + defer _encoderMutex.RUnlock() + if name == "" { + return nil, errNoEncoderNameSpecified + } + constructor, ok := _encoderNameToConstructor[name] + if !ok { + return nil, fmt.Errorf("no encoder registered for name %q", name) + } + return constructor(encoderConfig) +} diff --git a/vendor/go.uber.org/zap/encoder_test.go b/vendor/go.uber.org/zap/encoder_test.go new file mode 100644 index 0000000000..b71eb654b7 --- /dev/null +++ b/vendor/go.uber.org/zap/encoder_test.go @@ -0,0 +1,88 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "testing" + + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" +) + +func TestRegisterDefaultEncoders(t *testing.T) { + testEncodersRegistered(t, "console", "json") +} + +func TestRegisterEncoder(t *testing.T) { + testEncoders(func() { + assert.NoError(t, RegisterEncoder("foo", newNilEncoder), "expected to be able to register the encoder foo") + testEncodersRegistered(t, "foo") + }) +} + +func TestDuplicateRegisterEncoder(t *testing.T) { + testEncoders(func() { + assert.NoError(t, RegisterEncoder("foo", newNilEncoder), "expected to be able to register the encoder foo") + assert.Error(t, RegisterEncoder("foo", newNilEncoder), "expected an error when registering an encoder with the same name twice") + }) +} + +func TestRegisterEncoderNoName(t *testing.T) { + assert.Equal(t, errNoEncoderNameSpecified, RegisterEncoder("", newNilEncoder), "expected an error when registering an encoder with no name") +} + +func TestNewEncoder(t *testing.T) { + testEncoders(func() { + assert.NoError(t, RegisterEncoder("foo", newNilEncoder), "expected to be able to register the encoder foo") + encoder, err := newEncoder("foo", zapcore.EncoderConfig{}) + assert.NoError(t, err, "could not create an encoder for the registered name foo") + assert.Nil(t, encoder, "the encoder from newNilEncoder is not nil") + }) +} + +func TestNewEncoderNotRegistered(t *testing.T) { + _, err := newEncoder("foo", zapcore.EncoderConfig{}) + assert.Error(t, err, "expected an error when trying to create an encoder of an unregistered name") +} + +func TestNewEncoderNoName(t *testing.T) { + _, err := newEncoder("", zapcore.EncoderConfig{}) + assert.Equal(t, errNoEncoderNameSpecified, err, "expected an error when creating an encoder with no name") +} + +func testEncoders(f func()) { + existing := _encoderNameToConstructor + _encoderNameToConstructor = make(map[string]func(zapcore.EncoderConfig) (zapcore.Encoder, error)) + defer func() { _encoderNameToConstructor = existing }() + f() +} + +func testEncodersRegistered(t *testing.T, names ...string) { + assert.Len(t, _encoderNameToConstructor, len(names), "the expected number of registered encoders does not match the actual number") + for _, name := range names { + assert.NotNil(t, _encoderNameToConstructor[name], "no encoder is registered for name %s", name) + } +} + +func newNilEncoder(_ zapcore.EncoderConfig) (zapcore.Encoder, error) { + return nil, nil +} diff --git a/vendor/go.uber.org/zap/error.go b/vendor/go.uber.org/zap/error.go new file mode 100644 index 0000000000..45f7b838dc --- /dev/null +++ b/vendor/go.uber.org/zap/error.go @@ -0,0 +1,82 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "go.uber.org/zap/internal/pool" + "go.uber.org/zap/zapcore" +) + +var _errArrayElemPool = pool.New(func() *errArrayElem { + return &errArrayElem{} +}) + +// Error is shorthand for the common idiom NamedError("error", err). +func Error(err error) Field { + return NamedError("error", err) +} + +// NamedError constructs a field that lazily stores err.Error() under the +// provided key. Errors which also implement fmt.Formatter (like those produced +// by github.com/pkg/errors) will also have their verbose representation stored +// under key+"Verbose". If passed a nil error, the field is a no-op. +// +// For the common case in which the key is simply "error", the Error function +// is shorter and less repetitive. +func NamedError(key string, err error) Field { + if err == nil { + return Skip() + } + return Field{Key: key, Type: zapcore.ErrorType, Interface: err} +} + +type errArray []error + +func (errs errArray) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range errs { + if errs[i] == nil { + continue + } + // To represent each error as an object with an "error" attribute and + // potentially an "errorVerbose" attribute, we need to wrap it in a + // type that implements LogObjectMarshaler. To prevent this from + // allocating, pool the wrapper type. + elem := _errArrayElemPool.Get() + elem.error = errs[i] + err := arr.AppendObject(elem) + elem.error = nil + _errArrayElemPool.Put(elem) + if err != nil { + return err + } + } + return nil +} + +type errArrayElem struct { + error +} + +func (e *errArrayElem) MarshalLogObject(enc zapcore.ObjectEncoder) error { + // Re-use the error field's logic, which supports non-standard error types. + Error(e.error).AddTo(enc) + return nil +} diff --git a/vendor/go.uber.org/zap/error_test.go b/vendor/go.uber.org/zap/error_test.go new file mode 100644 index 0000000000..4bfa370d2a --- /dev/null +++ b/vendor/go.uber.org/zap/error_test.go @@ -0,0 +1,133 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "fmt" + "testing" + + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestErrorConstructors(t *testing.T) { + fail := errors.New("fail") + + tests := []struct { + name string + field Field + expect Field + }{ + {"Error", Skip(), Error(nil)}, + {"Error", Field{Key: "error", Type: zapcore.ErrorType, Interface: fail}, Error(fail)}, + {"NamedError", Skip(), NamedError("foo", nil)}, + {"NamedError", Field{Key: "foo", Type: zapcore.ErrorType, Interface: fail}, NamedError("foo", fail)}, + {"Any:Error", Any("k", errors.New("v")), NamedError("k", errors.New("v"))}, + {"Any:Errors", Any("k", []error{errors.New("v")}), Errors("k", []error{errors.New("v")})}, + } + + for _, tt := range tests { + if !assert.Equal(t, tt.expect, tt.field, "Unexpected output from convenience field constructor %s.", tt.name) { + t.Logf("type expected: %T\nGot: %T", tt.expect.Interface, tt.field.Interface) + } + assertCanBeReused(t, tt.field) + } +} + +func TestErrorArrayConstructor(t *testing.T) { + tests := []struct { + desc string + field Field + expected []interface{} + }{ + {"empty errors", Errors("", []error{}), []interface{}{}}, + { + "errors", + Errors("", []error{nil, errors.New("foo"), nil, errors.New("bar")}), + []interface{}{map[string]interface{}{"error": "foo"}, map[string]interface{}{"error": "bar"}}, + }, + } + + for _, tt := range tests { + enc := zapcore.NewMapObjectEncoder() + tt.field.Key = "k" + tt.field.AddTo(enc) + assert.Equal(t, tt.expected, enc.Fields["k"], "%s: unexpected map contents.", tt.desc) + assert.Equal(t, 1, len(enc.Fields), "%s: found extra keys in map: %v", tt.desc, enc.Fields) + } +} + +func TestErrorsArraysHandleRichErrors(t *testing.T) { + errs := []error{fmt.Errorf("egad")} + + enc := zapcore.NewMapObjectEncoder() + Errors("k", errs).AddTo(enc) + assert.Equal(t, 1, len(enc.Fields), "Expected only top-level field.") + + val := enc.Fields["k"] + arr, ok := val.([]interface{}) + require.True(t, ok, "Expected top-level field to be an array.") + require.Equal(t, 1, len(arr), "Expected only one error object in array.") + + serialized := arr[0] + errMap, ok := serialized.(map[string]interface{}) + require.True(t, ok, "Expected serialized error to be a map, got %T.", serialized) + assert.Equal(t, "egad", errMap["error"], "Unexpected standard error string.") +} + +func TestErrArrayBrokenEncoder(t *testing.T) { + t.Parallel() + + failWith := errors.New("great sadness") + err := (brokenArrayObjectEncoder{ + Err: failWith, + ObjectEncoder: zapcore.NewMapObjectEncoder(), + }).AddArray("errors", errArray{ + errors.New("foo"), + errors.New("bar"), + }) + require.Error(t, err, "Expected error from broken encoder.") + assert.ErrorIs(t, err, failWith, "Unexpected error.") +} + +// brokenArrayObjectEncoder is an ObjectEncoder +// that builds a broken ArrayEncoder. +type brokenArrayObjectEncoder struct { + zapcore.ObjectEncoder + zapcore.ArrayEncoder + + Err error // error to return +} + +func (enc brokenArrayObjectEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error { + return enc.ObjectEncoder.AddArray(key, + zapcore.ArrayMarshalerFunc(func(ae zapcore.ArrayEncoder) error { + enc.ArrayEncoder = ae + return marshaler.MarshalLogArray(enc) + })) +} + +func (enc brokenArrayObjectEncoder) AppendObject(zapcore.ObjectMarshaler) error { + return enc.Err +} diff --git a/vendor/go.uber.org/zap/example_test.go b/vendor/go.uber.org/zap/example_test.go new file mode 100644 index 0000000000..af7df0e25a --- /dev/null +++ b/vendor/go.uber.org/zap/example_test.go @@ -0,0 +1,413 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap_test + +import ( + "encoding/json" + "io" + "log" + "os" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func Example_presets() { + // Using zap's preset constructors is the simplest way to get a feel for the + // package, but they don't allow much customization. + logger := zap.NewExample() // or NewProduction, or NewDevelopment + defer logger.Sync() + + const url = "http://example.com" + + // In most circumstances, use the SugaredLogger. It's 4-10x faster than most + // other structured logging packages and has a familiar, loosely-typed API. + sugar := logger.Sugar() + sugar.Infow("Failed to fetch URL.", + // Structured context as loosely typed key-value pairs. + "url", url, + "attempt", 3, + "backoff", time.Second, + ) + sugar.Infof("Failed to fetch URL: %s", url) + + // In the unusual situations where every microsecond matters, use the + // Logger. It's even faster than the SugaredLogger, but only supports + // structured logging. + logger.Info("Failed to fetch URL.", + // Structured context as strongly typed fields. + zap.String("url", url), + zap.Int("attempt", 3), + zap.Duration("backoff", time.Second), + ) + // Output: + // {"level":"info","msg":"Failed to fetch URL.","url":"http://example.com","attempt":3,"backoff":"1s"} + // {"level":"info","msg":"Failed to fetch URL: http://example.com"} + // {"level":"info","msg":"Failed to fetch URL.","url":"http://example.com","attempt":3,"backoff":"1s"} +} + +func Example_basicConfiguration() { + // For some users, the presets offered by the NewProduction, NewDevelopment, + // and NewExample constructors won't be appropriate. For most of those + // users, the bundled Config struct offers the right balance of flexibility + // and convenience. (For more complex needs, see the AdvancedConfiguration + // example.) + // + // See the documentation for Config and zapcore.EncoderConfig for all the + // available options. + rawJSON := []byte(`{ + "level": "debug", + "encoding": "json", + "outputPaths": ["stdout", "/tmp/logs"], + "errorOutputPaths": ["stderr"], + "initialFields": {"foo": "bar"}, + "encoderConfig": { + "messageKey": "message", + "levelKey": "level", + "levelEncoder": "lowercase" + } + }`) + + var cfg zap.Config + if err := json.Unmarshal(rawJSON, &cfg); err != nil { + panic(err) + } + logger := zap.Must(cfg.Build()) + defer logger.Sync() + + logger.Info("logger construction succeeded") + // Output: + // {"level":"info","message":"logger construction succeeded","foo":"bar"} +} + +func Example_advancedConfiguration() { + // The bundled Config struct only supports the most common configuration + // options. More complex needs, like splitting logs between multiple files + // or writing to non-file outputs, require use of the zapcore package. + // + // In this example, imagine we're both sending our logs to Kafka and writing + // them to the console. We'd like to encode the console output and the Kafka + // topics differently, and we'd also like special treatment for + // high-priority logs. + + // First, define our level-handling logic. + highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { + return lvl >= zapcore.ErrorLevel + }) + lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { + return lvl < zapcore.ErrorLevel + }) + + // Assume that we have clients for two Kafka topics. The clients implement + // zapcore.WriteSyncer and are safe for concurrent use. (If they only + // implement io.Writer, we can use zapcore.AddSync to add a no-op Sync + // method. If they're not safe for concurrent use, we can add a protecting + // mutex with zapcore.Lock.) + topicDebugging := zapcore.AddSync(io.Discard) + topicErrors := zapcore.AddSync(io.Discard) + + // High-priority output should also go to standard error, and low-priority + // output should also go to standard out. + consoleDebugging := zapcore.Lock(os.Stdout) + consoleErrors := zapcore.Lock(os.Stderr) + + // Optimize the Kafka output for machine consumption and the console output + // for human operators. + kafkaEncoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) + consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) + + // Join the outputs, encoders, and level-handling functions into + // zapcore.Cores, then tee the four cores together. + core := zapcore.NewTee( + zapcore.NewCore(kafkaEncoder, topicErrors, highPriority), + zapcore.NewCore(consoleEncoder, consoleErrors, highPriority), + zapcore.NewCore(kafkaEncoder, topicDebugging, lowPriority), + zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority), + ) + + // From a zapcore.Core, it's easy to construct a Logger. + logger := zap.New(core) + defer logger.Sync() + logger.Info("constructed a logger") +} + +func ExampleNamespace() { + logger := zap.NewExample() + defer logger.Sync() + + logger.With( + zap.Namespace("metrics"), + zap.Int("counter", 1), + ).Info("tracked some metrics") + // Output: + // {"level":"info","msg":"tracked some metrics","metrics":{"counter":1}} +} + +type addr struct { + IP string + Port int +} + +type request struct { + URL string + Listen addr + Remote addr +} + +func (a addr) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("ip", a.IP) + enc.AddInt("port", a.Port) + return nil +} + +func (r *request) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("url", r.URL) + zap.Inline(r.Listen).AddTo(enc) + return enc.AddObject("remote", r.Remote) +} + +func ExampleObject() { + logger := zap.NewExample() + defer logger.Sync() + + req := &request{ + URL: "/test", + Listen: addr{"127.0.0.1", 8080}, + Remote: addr{"127.0.0.1", 31200}, + } + logger.Info("new request, in nested object", zap.Object("req", req)) + logger.Info("new request, inline", zap.Inline(req)) + // Output: + // {"level":"info","msg":"new request, in nested object","req":{"url":"/test","ip":"127.0.0.1","port":8080,"remote":{"ip":"127.0.0.1","port":31200}}} + // {"level":"info","msg":"new request, inline","url":"/test","ip":"127.0.0.1","port":8080,"remote":{"ip":"127.0.0.1","port":31200}} +} + +func ExampleNewStdLog() { + logger := zap.NewExample() + defer logger.Sync() + + std := zap.NewStdLog(logger) + std.Print("standard logger wrapper") + // Output: + // {"level":"info","msg":"standard logger wrapper"} +} + +func ExampleRedirectStdLog() { + logger := zap.NewExample() + defer logger.Sync() + + undo := zap.RedirectStdLog(logger) + defer undo() + + log.Print("redirected standard library") + // Output: + // {"level":"info","msg":"redirected standard library"} +} + +func ExampleReplaceGlobals() { + logger := zap.NewExample() + defer logger.Sync() + + undo := zap.ReplaceGlobals(logger) + defer undo() + + zap.L().Info("replaced zap's global loggers") + // Output: + // {"level":"info","msg":"replaced zap's global loggers"} +} + +func ExampleAtomicLevel() { + atom := zap.NewAtomicLevel() + + // To keep the example deterministic, disable timestamps in the output. + encoderCfg := zap.NewProductionEncoderConfig() + encoderCfg.TimeKey = "" + + logger := zap.New(zapcore.NewCore( + zapcore.NewJSONEncoder(encoderCfg), + zapcore.Lock(os.Stdout), + atom, + )) + defer logger.Sync() + + logger.Info("info logging enabled") + + atom.SetLevel(zap.ErrorLevel) + logger.Info("info logging disabled") + // Output: + // {"level":"info","msg":"info logging enabled"} +} + +func ExampleAtomicLevel_config() { + // The zap.Config struct includes an AtomicLevel. To use it, keep a + // reference to the Config. + rawJSON := []byte(`{ + "level": "info", + "outputPaths": ["stdout"], + "errorOutputPaths": ["stderr"], + "encoding": "json", + "encoderConfig": { + "messageKey": "message", + "levelKey": "level", + "levelEncoder": "lowercase" + } + }`) + var cfg zap.Config + if err := json.Unmarshal(rawJSON, &cfg); err != nil { + panic(err) + } + logger := zap.Must(cfg.Build()) + defer logger.Sync() + + logger.Info("info logging enabled") + + cfg.Level.SetLevel(zap.ErrorLevel) + logger.Info("info logging disabled") + // Output: + // {"level":"info","message":"info logging enabled"} +} + +func ExampleLogger_Check() { + logger := zap.NewExample() + defer logger.Sync() + + if ce := logger.Check(zap.DebugLevel, "debugging"); ce != nil { + // If debug-level log output isn't enabled or if zap's sampling would have + // dropped this log entry, we don't allocate the slice that holds these + // fields. + ce.Write( + zap.String("foo", "bar"), + zap.String("baz", "quux"), + ) + } + + // Output: + // {"level":"debug","msg":"debugging","foo":"bar","baz":"quux"} +} + +func ExampleLogger_Named() { + logger := zap.NewExample() + defer logger.Sync() + + // By default, Loggers are unnamed. + logger.Info("no name") + + // The first call to Named sets the Logger name. + main := logger.Named("main") + main.Info("main logger") + + // Additional calls to Named create a period-separated path. + main.Named("subpackage").Info("sub-logger") + // Output: + // {"level":"info","msg":"no name"} + // {"level":"info","logger":"main","msg":"main logger"} + // {"level":"info","logger":"main.subpackage","msg":"sub-logger"} +} + +func ExampleWrapCore_replace() { + // Replacing a Logger's core can alter fundamental behaviors. + // For example, it can convert a Logger to a no-op. + nop := zap.WrapCore(func(zapcore.Core) zapcore.Core { + return zapcore.NewNopCore() + }) + + logger := zap.NewExample() + defer logger.Sync() + + logger.Info("working") + logger.WithOptions(nop).Info("no-op") + logger.Info("original logger still works") + // Output: + // {"level":"info","msg":"working"} + // {"level":"info","msg":"original logger still works"} +} + +func ExampleWrapCore_wrap() { + // Wrapping a Logger's core can extend its functionality. As a trivial + // example, it can double-write all logs. + doubled := zap.WrapCore(func(c zapcore.Core) zapcore.Core { + return zapcore.NewTee(c, c) + }) + + logger := zap.NewExample() + defer logger.Sync() + + logger.Info("single") + logger.WithOptions(doubled).Info("doubled") + // Output: + // {"level":"info","msg":"single"} + // {"level":"info","msg":"doubled"} + // {"level":"info","msg":"doubled"} +} + +func ExampleDict() { + logger := zap.NewExample() + defer logger.Sync() + + logger.Info("login event", + zap.Dict("event", + zap.Int("id", 123), + zap.String("name", "jane"), + zap.String("status", "pending"))) + // Output: + // {"level":"info","msg":"login event","event":{"id":123,"name":"jane","status":"pending"}} +} + +func ExampleObjects() { + logger := zap.NewExample() + defer logger.Sync() + + // Use the Objects field constructor when you have a list of objects, + // all of which implement zapcore.ObjectMarshaler. + logger.Debug("opening connections", + zap.Objects("addrs", []addr{ + {IP: "123.45.67.89", Port: 4040}, + {IP: "127.0.0.1", Port: 4041}, + {IP: "192.168.0.1", Port: 4042}, + })) + // Output: + // {"level":"debug","msg":"opening connections","addrs":[{"ip":"123.45.67.89","port":4040},{"ip":"127.0.0.1","port":4041},{"ip":"192.168.0.1","port":4042}]} +} + +func ExampleObjectValues() { + logger := zap.NewExample() + defer logger.Sync() + + // Use the ObjectValues field constructor when you have a list of + // objects that do not implement zapcore.ObjectMarshaler directly, + // but on their pointer receivers. + logger.Debug("starting tunnels", + zap.ObjectValues("addrs", []request{ + { + URL: "/foo", + Listen: addr{"127.0.0.1", 8080}, + Remote: addr{"123.45.67.89", 4040}, + }, + { + URL: "/bar", + Listen: addr{"127.0.0.1", 8080}, + Remote: addr{"127.0.0.1", 31200}, + }, + })) + // Output: + // {"level":"debug","msg":"starting tunnels","addrs":[{"url":"/foo","ip":"127.0.0.1","port":8080,"remote":{"ip":"123.45.67.89","port":4040}},{"url":"/bar","ip":"127.0.0.1","port":8080,"remote":{"ip":"127.0.0.1","port":31200}}]} +} diff --git a/vendor/go.uber.org/zap/field.go b/vendor/go.uber.org/zap/field.go new file mode 100644 index 0000000000..c8dd3358a9 --- /dev/null +++ b/vendor/go.uber.org/zap/field.go @@ -0,0 +1,613 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "fmt" + "math" + "time" + + "go.uber.org/zap/internal/stacktrace" + "go.uber.org/zap/zapcore" +) + +// Field is an alias for Field. Aliasing this type dramatically +// improves the navigability of this package's API documentation. +type Field = zapcore.Field + +var ( + _minTimeInt64 = time.Unix(0, math.MinInt64) + _maxTimeInt64 = time.Unix(0, math.MaxInt64) +) + +// Skip constructs a no-op field, which is often useful when handling invalid +// inputs in other Field constructors. +func Skip() Field { + return Field{Type: zapcore.SkipType} +} + +// nilField returns a field which will marshal explicitly as nil. See motivation +// in https://github.com/uber-go/zap/issues/753 . If we ever make breaking +// changes and add zapcore.NilType and zapcore.ObjectEncoder.AddNil, the +// implementation here should be changed to reflect that. +func nilField(key string) Field { return Reflect(key, nil) } + +// Binary constructs a field that carries an opaque binary blob. +// +// Binary data is serialized in an encoding-appropriate format. For example, +// zap's JSON encoder base64-encodes binary blobs. To log UTF-8 encoded text, +// use ByteString. +func Binary(key string, val []byte) Field { + return Field{Key: key, Type: zapcore.BinaryType, Interface: val} +} + +// Bool constructs a field that carries a bool. +func Bool(key string, val bool) Field { + var ival int64 + if val { + ival = 1 + } + return Field{Key: key, Type: zapcore.BoolType, Integer: ival} +} + +// Boolp constructs a field that carries a *bool. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Boolp(key string, val *bool) Field { + if val == nil { + return nilField(key) + } + return Bool(key, *val) +} + +// ByteString constructs a field that carries UTF-8 encoded text as a []byte. +// To log opaque binary blobs (which aren't necessarily valid UTF-8), use +// Binary. +func ByteString(key string, val []byte) Field { + return Field{Key: key, Type: zapcore.ByteStringType, Interface: val} +} + +// Complex128 constructs a field that carries a complex number. Unlike most +// numeric fields, this costs an allocation (to convert the complex128 to +// interface{}). +func Complex128(key string, val complex128) Field { + return Field{Key: key, Type: zapcore.Complex128Type, Interface: val} +} + +// Complex128p constructs a field that carries a *complex128. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Complex128p(key string, val *complex128) Field { + if val == nil { + return nilField(key) + } + return Complex128(key, *val) +} + +// Complex64 constructs a field that carries a complex number. Unlike most +// numeric fields, this costs an allocation (to convert the complex64 to +// interface{}). +func Complex64(key string, val complex64) Field { + return Field{Key: key, Type: zapcore.Complex64Type, Interface: val} +} + +// Complex64p constructs a field that carries a *complex64. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Complex64p(key string, val *complex64) Field { + if val == nil { + return nilField(key) + } + return Complex64(key, *val) +} + +// Float64 constructs a field that carries a float64. The way the +// floating-point value is represented is encoder-dependent, so marshaling is +// necessarily lazy. +func Float64(key string, val float64) Field { + return Field{Key: key, Type: zapcore.Float64Type, Integer: int64(math.Float64bits(val))} +} + +// Float64p constructs a field that carries a *float64. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Float64p(key string, val *float64) Field { + if val == nil { + return nilField(key) + } + return Float64(key, *val) +} + +// Float32 constructs a field that carries a float32. The way the +// floating-point value is represented is encoder-dependent, so marshaling is +// necessarily lazy. +func Float32(key string, val float32) Field { + return Field{Key: key, Type: zapcore.Float32Type, Integer: int64(math.Float32bits(val))} +} + +// Float32p constructs a field that carries a *float32. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Float32p(key string, val *float32) Field { + if val == nil { + return nilField(key) + } + return Float32(key, *val) +} + +// Int constructs a field with the given key and value. +func Int(key string, val int) Field { + return Int64(key, int64(val)) +} + +// Intp constructs a field that carries a *int. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Intp(key string, val *int) Field { + if val == nil { + return nilField(key) + } + return Int(key, *val) +} + +// Int64 constructs a field with the given key and value. +func Int64(key string, val int64) Field { + return Field{Key: key, Type: zapcore.Int64Type, Integer: val} +} + +// Int64p constructs a field that carries a *int64. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Int64p(key string, val *int64) Field { + if val == nil { + return nilField(key) + } + return Int64(key, *val) +} + +// Int32 constructs a field with the given key and value. +func Int32(key string, val int32) Field { + return Field{Key: key, Type: zapcore.Int32Type, Integer: int64(val)} +} + +// Int32p constructs a field that carries a *int32. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Int32p(key string, val *int32) Field { + if val == nil { + return nilField(key) + } + return Int32(key, *val) +} + +// Int16 constructs a field with the given key and value. +func Int16(key string, val int16) Field { + return Field{Key: key, Type: zapcore.Int16Type, Integer: int64(val)} +} + +// Int16p constructs a field that carries a *int16. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Int16p(key string, val *int16) Field { + if val == nil { + return nilField(key) + } + return Int16(key, *val) +} + +// Int8 constructs a field with the given key and value. +func Int8(key string, val int8) Field { + return Field{Key: key, Type: zapcore.Int8Type, Integer: int64(val)} +} + +// Int8p constructs a field that carries a *int8. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Int8p(key string, val *int8) Field { + if val == nil { + return nilField(key) + } + return Int8(key, *val) +} + +// String constructs a field with the given key and value. +func String(key string, val string) Field { + return Field{Key: key, Type: zapcore.StringType, String: val} +} + +// Stringp constructs a field that carries a *string. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Stringp(key string, val *string) Field { + if val == nil { + return nilField(key) + } + return String(key, *val) +} + +// Uint constructs a field with the given key and value. +func Uint(key string, val uint) Field { + return Uint64(key, uint64(val)) +} + +// Uintp constructs a field that carries a *uint. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Uintp(key string, val *uint) Field { + if val == nil { + return nilField(key) + } + return Uint(key, *val) +} + +// Uint64 constructs a field with the given key and value. +func Uint64(key string, val uint64) Field { + return Field{Key: key, Type: zapcore.Uint64Type, Integer: int64(val)} +} + +// Uint64p constructs a field that carries a *uint64. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Uint64p(key string, val *uint64) Field { + if val == nil { + return nilField(key) + } + return Uint64(key, *val) +} + +// Uint32 constructs a field with the given key and value. +func Uint32(key string, val uint32) Field { + return Field{Key: key, Type: zapcore.Uint32Type, Integer: int64(val)} +} + +// Uint32p constructs a field that carries a *uint32. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Uint32p(key string, val *uint32) Field { + if val == nil { + return nilField(key) + } + return Uint32(key, *val) +} + +// Uint16 constructs a field with the given key and value. +func Uint16(key string, val uint16) Field { + return Field{Key: key, Type: zapcore.Uint16Type, Integer: int64(val)} +} + +// Uint16p constructs a field that carries a *uint16. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Uint16p(key string, val *uint16) Field { + if val == nil { + return nilField(key) + } + return Uint16(key, *val) +} + +// Uint8 constructs a field with the given key and value. +func Uint8(key string, val uint8) Field { + return Field{Key: key, Type: zapcore.Uint8Type, Integer: int64(val)} +} + +// Uint8p constructs a field that carries a *uint8. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Uint8p(key string, val *uint8) Field { + if val == nil { + return nilField(key) + } + return Uint8(key, *val) +} + +// Uintptr constructs a field with the given key and value. +func Uintptr(key string, val uintptr) Field { + return Field{Key: key, Type: zapcore.UintptrType, Integer: int64(val)} +} + +// Uintptrp constructs a field that carries a *uintptr. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Uintptrp(key string, val *uintptr) Field { + if val == nil { + return nilField(key) + } + return Uintptr(key, *val) +} + +// Reflect constructs a field with the given key and an arbitrary object. It uses +// an encoding-appropriate, reflection-based function to lazily serialize nearly +// any object into the logging context, but it's relatively slow and +// allocation-heavy. Outside tests, Any is always a better choice. +// +// If encoding fails (e.g., trying to serialize a map[int]string to JSON), Reflect +// includes the error message in the final log output. +func Reflect(key string, val interface{}) Field { + return Field{Key: key, Type: zapcore.ReflectType, Interface: val} +} + +// Namespace creates a named, isolated scope within the logger's context. All +// subsequent fields will be added to the new namespace. +// +// This helps prevent key collisions when injecting loggers into sub-components +// or third-party libraries. +func Namespace(key string) Field { + return Field{Key: key, Type: zapcore.NamespaceType} +} + +// Stringer constructs a field with the given key and the output of the value's +// String method. The Stringer's String method is called lazily. +func Stringer(key string, val fmt.Stringer) Field { + return Field{Key: key, Type: zapcore.StringerType, Interface: val} +} + +// Time constructs a Field with the given key and value. The encoder +// controls how the time is serialized. +func Time(key string, val time.Time) Field { + if val.Before(_minTimeInt64) || val.After(_maxTimeInt64) { + return Field{Key: key, Type: zapcore.TimeFullType, Interface: val} + } + return Field{Key: key, Type: zapcore.TimeType, Integer: val.UnixNano(), Interface: val.Location()} +} + +// Timep constructs a field that carries a *time.Time. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Timep(key string, val *time.Time) Field { + if val == nil { + return nilField(key) + } + return Time(key, *val) +} + +// Stack constructs a field that stores a stacktrace of the current goroutine +// under provided key. Keep in mind that taking a stacktrace is eager and +// expensive (relatively speaking); this function both makes an allocation and +// takes about two microseconds. +func Stack(key string) Field { + return StackSkip(key, 1) // skip Stack +} + +// StackSkip constructs a field similarly to Stack, but also skips the given +// number of frames from the top of the stacktrace. +func StackSkip(key string, skip int) Field { + // Returning the stacktrace as a string costs an allocation, but saves us + // from expanding the zapcore.Field union struct to include a byte slice. Since + // taking a stacktrace is already so expensive (~10us), the extra allocation + // is okay. + return String(key, stacktrace.Take(skip+1)) // skip StackSkip +} + +// Duration constructs a field with the given key and value. The encoder +// controls how the duration is serialized. +func Duration(key string, val time.Duration) Field { + return Field{Key: key, Type: zapcore.DurationType, Integer: int64(val)} +} + +// Durationp constructs a field that carries a *time.Duration. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Durationp(key string, val *time.Duration) Field { + if val == nil { + return nilField(key) + } + return Duration(key, *val) +} + +// Object constructs a field with the given key and ObjectMarshaler. It +// provides a flexible, but still type-safe and efficient, way to add map- or +// struct-like user-defined types to the logging context. The struct's +// MarshalLogObject method is called lazily. +func Object(key string, val zapcore.ObjectMarshaler) Field { + return Field{Key: key, Type: zapcore.ObjectMarshalerType, Interface: val} +} + +// Inline constructs a Field that is similar to Object, but it +// will add the elements of the provided ObjectMarshaler to the +// current namespace. +func Inline(val zapcore.ObjectMarshaler) Field { + return zapcore.Field{ + Type: zapcore.InlineMarshalerType, + Interface: val, + } +} + +// Dict constructs a field containing the provided key-value pairs. +// It acts similar to [Object], but with the fields specified as arguments. +func Dict(key string, val ...Field) Field { + return dictField(key, val) +} + +// We need a function with the signature (string, T) for zap.Any. +func dictField(key string, val []Field) Field { + return Object(key, dictObject(val)) +} + +type dictObject []Field + +func (d dictObject) MarshalLogObject(enc zapcore.ObjectEncoder) error { + for _, f := range d { + f.AddTo(enc) + } + return nil +} + +// We discovered an issue where zap.Any can cause a performance degradation +// when used in new goroutines. +// +// This happens because the compiler assigns 4.8kb (one zap.Field per arm of +// switch statement) of stack space for zap.Any when it takes the form: +// +// switch v := v.(type) { +// case string: +// return String(key, v) +// case int: +// return Int(key, v) +// // ... +// default: +// return Reflect(key, v) +// } +// +// To avoid this, we use the type switch to assign a value to a single local variable +// and then call a function on it. +// The local variable is just a function reference so it doesn't allocate +// when converted to an interface{}. +// +// A fair bit of experimentation went into this. +// See also: +// +// - https://github.com/uber-go/zap/pull/1301 +// - https://github.com/uber-go/zap/pull/1303 +// - https://github.com/uber-go/zap/pull/1304 +// - https://github.com/uber-go/zap/pull/1305 +// - https://github.com/uber-go/zap/pull/1308 +type anyFieldC[T any] func(string, T) Field + +func (f anyFieldC[T]) Any(key string, val any) Field { + v, _ := val.(T) + // val is guaranteed to be a T, except when it's nil. + return f(key, v) +} + +// Any takes a key and an arbitrary value and chooses the best way to represent +// them as a field, falling back to a reflection-based approach only if +// necessary. +// +// Since byte/uint8 and rune/int32 are aliases, Any can't differentiate between +// them. To minimize surprises, []byte values are treated as binary blobs, byte +// values are treated as uint8, and runes are always treated as integers. +func Any(key string, value interface{}) Field { + var c interface{ Any(string, any) Field } + + switch value.(type) { + case zapcore.ObjectMarshaler: + c = anyFieldC[zapcore.ObjectMarshaler](Object) + case zapcore.ArrayMarshaler: + c = anyFieldC[zapcore.ArrayMarshaler](Array) + case []Field: + c = anyFieldC[[]Field](dictField) + case bool: + c = anyFieldC[bool](Bool) + case *bool: + c = anyFieldC[*bool](Boolp) + case []bool: + c = anyFieldC[[]bool](Bools) + case complex128: + c = anyFieldC[complex128](Complex128) + case *complex128: + c = anyFieldC[*complex128](Complex128p) + case []complex128: + c = anyFieldC[[]complex128](Complex128s) + case complex64: + c = anyFieldC[complex64](Complex64) + case *complex64: + c = anyFieldC[*complex64](Complex64p) + case []complex64: + c = anyFieldC[[]complex64](Complex64s) + case float64: + c = anyFieldC[float64](Float64) + case *float64: + c = anyFieldC[*float64](Float64p) + case []float64: + c = anyFieldC[[]float64](Float64s) + case float32: + c = anyFieldC[float32](Float32) + case *float32: + c = anyFieldC[*float32](Float32p) + case []float32: + c = anyFieldC[[]float32](Float32s) + case int: + c = anyFieldC[int](Int) + case *int: + c = anyFieldC[*int](Intp) + case []int: + c = anyFieldC[[]int](Ints) + case int64: + c = anyFieldC[int64](Int64) + case *int64: + c = anyFieldC[*int64](Int64p) + case []int64: + c = anyFieldC[[]int64](Int64s) + case int32: + c = anyFieldC[int32](Int32) + case *int32: + c = anyFieldC[*int32](Int32p) + case []int32: + c = anyFieldC[[]int32](Int32s) + case int16: + c = anyFieldC[int16](Int16) + case *int16: + c = anyFieldC[*int16](Int16p) + case []int16: + c = anyFieldC[[]int16](Int16s) + case int8: + c = anyFieldC[int8](Int8) + case *int8: + c = anyFieldC[*int8](Int8p) + case []int8: + c = anyFieldC[[]int8](Int8s) + case string: + c = anyFieldC[string](String) + case *string: + c = anyFieldC[*string](Stringp) + case []string: + c = anyFieldC[[]string](Strings) + case uint: + c = anyFieldC[uint](Uint) + case *uint: + c = anyFieldC[*uint](Uintp) + case []uint: + c = anyFieldC[[]uint](Uints) + case uint64: + c = anyFieldC[uint64](Uint64) + case *uint64: + c = anyFieldC[*uint64](Uint64p) + case []uint64: + c = anyFieldC[[]uint64](Uint64s) + case uint32: + c = anyFieldC[uint32](Uint32) + case *uint32: + c = anyFieldC[*uint32](Uint32p) + case []uint32: + c = anyFieldC[[]uint32](Uint32s) + case uint16: + c = anyFieldC[uint16](Uint16) + case *uint16: + c = anyFieldC[*uint16](Uint16p) + case []uint16: + c = anyFieldC[[]uint16](Uint16s) + case uint8: + c = anyFieldC[uint8](Uint8) + case *uint8: + c = anyFieldC[*uint8](Uint8p) + case []byte: + c = anyFieldC[[]byte](Binary) + case uintptr: + c = anyFieldC[uintptr](Uintptr) + case *uintptr: + c = anyFieldC[*uintptr](Uintptrp) + case []uintptr: + c = anyFieldC[[]uintptr](Uintptrs) + case time.Time: + c = anyFieldC[time.Time](Time) + case *time.Time: + c = anyFieldC[*time.Time](Timep) + case []time.Time: + c = anyFieldC[[]time.Time](Times) + case time.Duration: + c = anyFieldC[time.Duration](Duration) + case *time.Duration: + c = anyFieldC[*time.Duration](Durationp) + case []time.Duration: + c = anyFieldC[[]time.Duration](Durations) + case error: + c = anyFieldC[error](NamedError) + case []error: + c = anyFieldC[[]error](Errors) + case fmt.Stringer: + c = anyFieldC[fmt.Stringer](Stringer) + default: + c = anyFieldC[any](Reflect) + } + + return c.Any(key, value) +} diff --git a/vendor/go.uber.org/zap/field_test.go b/vendor/go.uber.org/zap/field_test.go new file mode 100644 index 0000000000..f87f1592e4 --- /dev/null +++ b/vendor/go.uber.org/zap/field_test.go @@ -0,0 +1,316 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "math" + "net" + "regexp" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap/internal/stacktrace" + "go.uber.org/zap/zapcore" +) + +type username string + +func (n username) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("username", string(n)) + return nil +} + +func assertCanBeReused(t testing.TB, field Field) { + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + enc := zapcore.NewMapObjectEncoder() + + // Ensure using the field in multiple encoders in separate goroutines + // does not cause any races or panics. + wg.Add(1) + go func() { + defer wg.Done() + assert.NotPanics(t, func() { + field.AddTo(enc) + }, "Reusing a field should not cause issues") + }() + } + + wg.Wait() +} + +func TestFieldConstructors(t *testing.T) { + // Interface types. + addr := net.ParseIP("1.2.3.4") + name := username("phil") + ints := []int{5, 6} + + // Helpful values for use in constructing pointers to primitives below. + var ( + boolVal = bool(true) + complex128Val = complex128(complex(0, 0)) + complex64Val = complex64(complex(0, 0)) + durationVal = time.Duration(time.Second) + float64Val = float64(1.0) + float32Val = float32(1.0) + intVal = int(1) + int64Val = int64(1) + int32Val = int32(1) + int16Val = int16(1) + int8Val = int8(1) + stringVal = string("hello") + timeVal = time.Unix(100000, 0) + uintVal = uint(1) + uint64Val = uint64(1) + uint32Val = uint32(1) + uint16Val = uint16(1) + uint8Val = uint8(1) + uintptrVal = uintptr(1) + nilErr error + ) + + tests := []struct { + name string + field Field + expect Field + }{ + {"Skip", Field{Type: zapcore.SkipType}, Skip()}, + {"Binary", Field{Key: "k", Type: zapcore.BinaryType, Interface: []byte("ab12")}, Binary("k", []byte("ab12"))}, + {"Bool", Field{Key: "k", Type: zapcore.BoolType, Integer: 1}, Bool("k", true)}, + {"Bool", Field{Key: "k", Type: zapcore.BoolType, Integer: 1}, Bool("k", true)}, + {"ByteString", Field{Key: "k", Type: zapcore.ByteStringType, Interface: []byte("ab12")}, ByteString("k", []byte("ab12"))}, + {"Complex128", Field{Key: "k", Type: zapcore.Complex128Type, Interface: 1 + 2i}, Complex128("k", 1+2i)}, + {"Complex64", Field{Key: "k", Type: zapcore.Complex64Type, Interface: complex64(1 + 2i)}, Complex64("k", 1+2i)}, + {"Duration", Field{Key: "k", Type: zapcore.DurationType, Integer: 1}, Duration("k", 1)}, + {"Int", Field{Key: "k", Type: zapcore.Int64Type, Integer: 1}, Int("k", 1)}, + {"Int64", Field{Key: "k", Type: zapcore.Int64Type, Integer: 1}, Int64("k", 1)}, + {"Int32", Field{Key: "k", Type: zapcore.Int32Type, Integer: 1}, Int32("k", 1)}, + {"Int16", Field{Key: "k", Type: zapcore.Int16Type, Integer: 1}, Int16("k", 1)}, + {"Int8", Field{Key: "k", Type: zapcore.Int8Type, Integer: 1}, Int8("k", 1)}, + {"String", Field{Key: "k", Type: zapcore.StringType, String: "foo"}, String("k", "foo")}, + {"Time", Field{Key: "k", Type: zapcore.TimeType, Integer: 0, Interface: time.UTC}, Time("k", time.Unix(0, 0).In(time.UTC))}, + {"Time", Field{Key: "k", Type: zapcore.TimeType, Integer: 1000, Interface: time.UTC}, Time("k", time.Unix(0, 1000).In(time.UTC))}, + {"Time", Field{Key: "k", Type: zapcore.TimeType, Integer: math.MinInt64, Interface: time.UTC}, Time("k", time.Unix(0, math.MinInt64).In(time.UTC))}, + {"Time", Field{Key: "k", Type: zapcore.TimeType, Integer: math.MaxInt64, Interface: time.UTC}, Time("k", time.Unix(0, math.MaxInt64).In(time.UTC))}, + {"Time", Field{Key: "k", Type: zapcore.TimeFullType, Interface: time.Time{}}, Time("k", time.Time{})}, + {"Time", Field{Key: "k", Type: zapcore.TimeFullType, Interface: time.Unix(math.MaxInt64, 0)}, Time("k", time.Unix(math.MaxInt64, 0))}, + {"Uint", Field{Key: "k", Type: zapcore.Uint64Type, Integer: 1}, Uint("k", 1)}, + {"Uint64", Field{Key: "k", Type: zapcore.Uint64Type, Integer: 1}, Uint64("k", 1)}, + {"Uint32", Field{Key: "k", Type: zapcore.Uint32Type, Integer: 1}, Uint32("k", 1)}, + {"Uint16", Field{Key: "k", Type: zapcore.Uint16Type, Integer: 1}, Uint16("k", 1)}, + {"Uint8", Field{Key: "k", Type: zapcore.Uint8Type, Integer: 1}, Uint8("k", 1)}, + {"Uintptr", Field{Key: "k", Type: zapcore.UintptrType, Integer: 10}, Uintptr("k", 0xa)}, + {"Reflect", Field{Key: "k", Type: zapcore.ReflectType, Interface: ints}, Reflect("k", ints)}, + {"Reflect", Field{Key: "k", Type: zapcore.ReflectType}, Reflect("k", nil)}, + {"Stringer", Field{Key: "k", Type: zapcore.StringerType, Interface: addr}, Stringer("k", addr)}, + {"Object", Field{Key: "k", Type: zapcore.ObjectMarshalerType, Interface: name}, Object("k", name)}, + {"Inline", Field{Type: zapcore.InlineMarshalerType, Interface: name}, Inline(name)}, + {"Any:ObjectMarshaler", Any("k", name), Object("k", name)}, + {"Any:ArrayMarshaler", Any("k", bools([]bool{true})), Array("k", bools([]bool{true}))}, + {"Any:Dict", Any("k", []Field{String("k", "v")}), Dict("k", String("k", "v"))}, + {"Any:Stringer", Any("k", addr), Stringer("k", addr)}, + {"Any:Bool", Any("k", true), Bool("k", true)}, + {"Any:Bools", Any("k", []bool{true}), Bools("k", []bool{true})}, + {"Any:Byte", Any("k", byte(1)), Uint8("k", 1)}, + {"Any:Bytes", Any("k", []byte{1}), Binary("k", []byte{1})}, + {"Any:Complex128", Any("k", 1+2i), Complex128("k", 1+2i)}, + {"Any:Complex128s", Any("k", []complex128{1 + 2i}), Complex128s("k", []complex128{1 + 2i})}, + {"Any:Complex64", Any("k", complex64(1+2i)), Complex64("k", 1+2i)}, + {"Any:Complex64s", Any("k", []complex64{1 + 2i}), Complex64s("k", []complex64{1 + 2i})}, + {"Any:Float64", Any("k", 3.14), Float64("k", 3.14)}, + {"Any:Float64s", Any("k", []float64{3.14}), Float64s("k", []float64{3.14})}, + {"Any:Float32", Any("k", float32(3.14)), Float32("k", 3.14)}, + {"Any:Float32s", Any("k", []float32{3.14}), Float32s("k", []float32{3.14})}, + {"Any:Int", Any("k", 1), Int("k", 1)}, + {"Any:Ints", Any("k", []int{1}), Ints("k", []int{1})}, + {"Any:Int64", Any("k", int64(1)), Int64("k", 1)}, + {"Any:Int64s", Any("k", []int64{1}), Int64s("k", []int64{1})}, + {"Any:Int32", Any("k", int32(1)), Int32("k", 1)}, + {"Any:Int32s", Any("k", []int32{1}), Int32s("k", []int32{1})}, + {"Any:Int16", Any("k", int16(1)), Int16("k", 1)}, + {"Any:Int16s", Any("k", []int16{1}), Int16s("k", []int16{1})}, + {"Any:Int8", Any("k", int8(1)), Int8("k", 1)}, + {"Any:Int8s", Any("k", []int8{1}), Int8s("k", []int8{1})}, + {"Any:Rune", Any("k", rune(1)), Int32("k", 1)}, + {"Any:Runes", Any("k", []rune{1}), Int32s("k", []int32{1})}, + {"Any:String", Any("k", "v"), String("k", "v")}, + {"Any:Strings", Any("k", []string{"v"}), Strings("k", []string{"v"})}, + {"Any:Uint", Any("k", uint(1)), Uint("k", 1)}, + {"Any:Uints", Any("k", []uint{1}), Uints("k", []uint{1})}, + {"Any:Uint64", Any("k", uint64(1)), Uint64("k", 1)}, + {"Any:Uint64s", Any("k", []uint64{1}), Uint64s("k", []uint64{1})}, + {"Any:Uint32", Any("k", uint32(1)), Uint32("k", 1)}, + {"Any:Uint32s", Any("k", []uint32{1}), Uint32s("k", []uint32{1})}, + {"Any:Uint16", Any("k", uint16(1)), Uint16("k", 1)}, + {"Any:Uint16s", Any("k", []uint16{1}), Uint16s("k", []uint16{1})}, + {"Any:Uint8", Any("k", uint8(1)), Uint8("k", 1)}, + {"Any:Uint8s", Any("k", []uint8{1}), Binary("k", []uint8{1})}, + {"Any:Uintptr", Any("k", uintptr(1)), Uintptr("k", 1)}, + {"Any:Uintptrs", Any("k", []uintptr{1}), Uintptrs("k", []uintptr{1})}, + {"Any:Time", Any("k", time.Unix(0, 0)), Time("k", time.Unix(0, 0))}, + {"Any:TimeFullType", Any("k", time.Time{}), Time("k", time.Time{})}, + {"Any:Times", Any("k", []time.Time{time.Unix(0, 0)}), Times("k", []time.Time{time.Unix(0, 0)})}, + {"Any:Duration", Any("k", time.Second), Duration("k", time.Second)}, + {"Any:Durations", Any("k", []time.Duration{time.Second}), Durations("k", []time.Duration{time.Second})}, + {"Any:Fallback", Any("k", struct{}{}), Reflect("k", struct{}{})}, + {"Ptr:Bool", Boolp("k", nil), nilField("k")}, + {"Ptr:Bool", Boolp("k", &boolVal), Bool("k", boolVal)}, + {"Any:PtrBool", Any("k", (*bool)(nil)), nilField("k")}, + {"Any:PtrBool", Any("k", &boolVal), Bool("k", boolVal)}, + {"Ptr:Complex128", Complex128p("k", nil), nilField("k")}, + {"Ptr:Complex128", Complex128p("k", &complex128Val), Complex128("k", complex128Val)}, + {"Any:PtrComplex128", Any("k", (*complex128)(nil)), nilField("k")}, + {"Any:PtrComplex128", Any("k", &complex128Val), Complex128("k", complex128Val)}, + {"Ptr:Complex64", Complex64p("k", nil), nilField("k")}, + {"Ptr:Complex64", Complex64p("k", &complex64Val), Complex64("k", complex64Val)}, + {"Any:PtrComplex64", Any("k", (*complex64)(nil)), nilField("k")}, + {"Any:PtrComplex64", Any("k", &complex64Val), Complex64("k", complex64Val)}, + {"Ptr:Duration", Durationp("k", nil), nilField("k")}, + {"Ptr:Duration", Durationp("k", &durationVal), Duration("k", durationVal)}, + {"Any:PtrDuration", Any("k", (*time.Duration)(nil)), nilField("k")}, + {"Any:PtrDuration", Any("k", &durationVal), Duration("k", durationVal)}, + {"Ptr:Float64", Float64p("k", nil), nilField("k")}, + {"Ptr:Float64", Float64p("k", &float64Val), Float64("k", float64Val)}, + {"Any:PtrFloat64", Any("k", (*float64)(nil)), nilField("k")}, + {"Any:PtrFloat64", Any("k", &float64Val), Float64("k", float64Val)}, + {"Ptr:Float32", Float32p("k", nil), nilField("k")}, + {"Ptr:Float32", Float32p("k", &float32Val), Float32("k", float32Val)}, + {"Any:PtrFloat32", Any("k", (*float32)(nil)), nilField("k")}, + {"Any:PtrFloat32", Any("k", &float32Val), Float32("k", float32Val)}, + {"Ptr:Int", Intp("k", nil), nilField("k")}, + {"Ptr:Int", Intp("k", &intVal), Int("k", intVal)}, + {"Any:PtrInt", Any("k", (*int)(nil)), nilField("k")}, + {"Any:PtrInt", Any("k", &intVal), Int("k", intVal)}, + {"Ptr:Int64", Int64p("k", nil), nilField("k")}, + {"Ptr:Int64", Int64p("k", &int64Val), Int64("k", int64Val)}, + {"Any:PtrInt64", Any("k", (*int64)(nil)), nilField("k")}, + {"Any:PtrInt64", Any("k", &int64Val), Int64("k", int64Val)}, + {"Ptr:Int32", Int32p("k", nil), nilField("k")}, + {"Ptr:Int32", Int32p("k", &int32Val), Int32("k", int32Val)}, + {"Any:PtrInt32", Any("k", (*int32)(nil)), nilField("k")}, + {"Any:PtrInt32", Any("k", &int32Val), Int32("k", int32Val)}, + {"Ptr:Int16", Int16p("k", nil), nilField("k")}, + {"Ptr:Int16", Int16p("k", &int16Val), Int16("k", int16Val)}, + {"Any:PtrInt16", Any("k", (*int16)(nil)), nilField("k")}, + {"Any:PtrInt16", Any("k", &int16Val), Int16("k", int16Val)}, + {"Ptr:Int8", Int8p("k", nil), nilField("k")}, + {"Ptr:Int8", Int8p("k", &int8Val), Int8("k", int8Val)}, + {"Any:PtrInt8", Any("k", (*int8)(nil)), nilField("k")}, + {"Any:PtrInt8", Any("k", &int8Val), Int8("k", int8Val)}, + {"Ptr:String", Stringp("k", nil), nilField("k")}, + {"Ptr:String", Stringp("k", &stringVal), String("k", stringVal)}, + {"Any:PtrString", Any("k", (*string)(nil)), nilField("k")}, + {"Any:PtrString", Any("k", &stringVal), String("k", stringVal)}, + {"Ptr:Time", Timep("k", nil), nilField("k")}, + {"Ptr:Time", Timep("k", &timeVal), Time("k", timeVal)}, + {"Any:PtrTime", Any("k", (*time.Time)(nil)), nilField("k")}, + {"Any:PtrTime", Any("k", &timeVal), Time("k", timeVal)}, + {"Any:PtrTimeFullType", Any("k", &time.Time{}), Time("k", time.Time{})}, + {"Ptr:Uint", Uintp("k", nil), nilField("k")}, + {"Ptr:Uint", Uintp("k", &uintVal), Uint("k", uintVal)}, + {"Any:PtrUint", Any("k", (*uint)(nil)), nilField("k")}, + {"Any:PtrUint", Any("k", &uintVal), Uint("k", uintVal)}, + {"Ptr:Uint64", Uint64p("k", nil), nilField("k")}, + {"Ptr:Uint64", Uint64p("k", &uint64Val), Uint64("k", uint64Val)}, + {"Any:PtrUint64", Any("k", (*uint64)(nil)), nilField("k")}, + {"Any:PtrUint64", Any("k", &uint64Val), Uint64("k", uint64Val)}, + {"Ptr:Uint32", Uint32p("k", nil), nilField("k")}, + {"Ptr:Uint32", Uint32p("k", &uint32Val), Uint32("k", uint32Val)}, + {"Any:PtrUint32", Any("k", (*uint32)(nil)), nilField("k")}, + {"Any:PtrUint32", Any("k", &uint32Val), Uint32("k", uint32Val)}, + {"Ptr:Uint16", Uint16p("k", nil), nilField("k")}, + {"Ptr:Uint16", Uint16p("k", &uint16Val), Uint16("k", uint16Val)}, + {"Any:PtrUint16", Any("k", (*uint16)(nil)), nilField("k")}, + {"Any:PtrUint16", Any("k", &uint16Val), Uint16("k", uint16Val)}, + {"Ptr:Uint8", Uint8p("k", nil), nilField("k")}, + {"Ptr:Uint8", Uint8p("k", &uint8Val), Uint8("k", uint8Val)}, + {"Any:PtrUint8", Any("k", (*uint8)(nil)), nilField("k")}, + {"Any:PtrUint8", Any("k", &uint8Val), Uint8("k", uint8Val)}, + {"Ptr:Uintptr", Uintptrp("k", nil), nilField("k")}, + {"Ptr:Uintptr", Uintptrp("k", &uintptrVal), Uintptr("k", uintptrVal)}, + {"Any:PtrUintptr", Any("k", (*uintptr)(nil)), nilField("k")}, + {"Any:PtrUintptr", Any("k", &uintptrVal), Uintptr("k", uintptrVal)}, + {"Any:ErrorNil", Any("k", nilErr), nilField("k")}, + {"Namespace", Namespace("k"), Field{Key: "k", Type: zapcore.NamespaceType}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !assert.Equal(t, tt.expect, tt.field, "Unexpected output from convenience field constructor") { + t.Logf("type expected: %T\nGot: %T", tt.expect.Interface, tt.field.Interface) + } + assertCanBeReused(t, tt.field) + }) + } +} + +func TestStackField(t *testing.T) { + f := Stack("stacktrace") + assert.Equal(t, "stacktrace", f.Key, "Unexpected field key.") + assert.Equal(t, zapcore.StringType, f.Type, "Unexpected field type.") + r := regexp.MustCompile(`field_test.go:(\d+)`) + assert.Equal(t, r.ReplaceAllString(stacktrace.Take(0), "field_test.go"), r.ReplaceAllString(f.String, "field_test.go"), "Unexpected stack trace") + assertCanBeReused(t, f) +} + +func TestStackSkipField(t *testing.T) { + f := StackSkip("stacktrace", 0) + assert.Equal(t, "stacktrace", f.Key, "Unexpected field key.") + assert.Equal(t, zapcore.StringType, f.Type, "Unexpected field type.") + r := regexp.MustCompile(`field_test.go:(\d+)`) + assert.Equal(t, r.ReplaceAllString(stacktrace.Take(0), "field_test.go"), r.ReplaceAllString(f.String, "field_test.go"), f.String, "Unexpected stack trace") + assertCanBeReused(t, f) +} + +func TestStackSkipFieldWithSkip(t *testing.T) { + f := StackSkip("stacktrace", 1) + assert.Equal(t, "stacktrace", f.Key, "Unexpected field key.") + assert.Equal(t, zapcore.StringType, f.Type, "Unexpected field type.") + assert.Equal(t, stacktrace.Take(1), f.String, "Unexpected stack trace") + assertCanBeReused(t, f) +} + +func TestDict(t *testing.T) { + tests := []struct { + desc string + field Field + expected any + }{ + {"empty", Dict(""), map[string]any{}}, + {"single", Dict("", String("k", "v")), map[string]any{"k": "v"}}, + {"multiple", Dict("", String("k", "v"), String("k2", "v2")), map[string]any{"k": "v", "k2": "v2"}}, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + enc := zapcore.NewMapObjectEncoder() + tt.field.Key = "k" + tt.field.AddTo(enc) + assert.Equal(t, tt.expected, enc.Fields["k"], "unexpected map contents") + assert.Len(t, enc.Fields, 1, "found extra keys in map: %v", enc.Fields) + + assertCanBeReused(t, tt.field) + }) + } +} diff --git a/vendor/go.uber.org/zap/flag.go b/vendor/go.uber.org/zap/flag.go new file mode 100644 index 0000000000..1312875072 --- /dev/null +++ b/vendor/go.uber.org/zap/flag.go @@ -0,0 +1,39 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "flag" + + "go.uber.org/zap/zapcore" +) + +// LevelFlag uses the standard library's flag.Var to declare a global flag +// with the specified name, default, and usage guidance. The returned value is +// a pointer to the value of the flag. +// +// If you don't want to use the flag package's global state, you can use any +// non-nil *Level as a flag.Value with your own *flag.FlagSet. +func LevelFlag(name string, defaultLevel zapcore.Level, usage string) *zapcore.Level { + lvl := defaultLevel + flag.Var(&lvl, name, usage) + return &lvl +} diff --git a/vendor/go.uber.org/zap/flag_test.go b/vendor/go.uber.org/zap/flag_test.go new file mode 100644 index 0000000000..9ff5444d56 --- /dev/null +++ b/vendor/go.uber.org/zap/flag_test.go @@ -0,0 +1,103 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "flag" + "io" + "testing" + + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" +) + +type flagTestCase struct { + args []string + wantLevel zapcore.Level + wantErr bool +} + +func (tc flagTestCase) runImplicitSet(t testing.TB) { + origCommandLine := flag.CommandLine + flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError) + flag.CommandLine.SetOutput(io.Discard) + defer func() { flag.CommandLine = origCommandLine }() + + level := LevelFlag("level", InfoLevel, "") + tc.run(t, flag.CommandLine, level) +} + +func (tc flagTestCase) runExplicitSet(t testing.TB) { + var lvl zapcore.Level + set := flag.NewFlagSet("test", flag.ContinueOnError) + set.SetOutput(io.Discard) + set.Var(&lvl, "level", "minimum enabled logging level") + tc.run(t, set, &lvl) +} + +func (tc flagTestCase) run(t testing.TB, set *flag.FlagSet, actual *zapcore.Level) { + err := set.Parse(tc.args) + if tc.wantErr { + assert.Error(t, err, "Parse(%v) should fail.", tc.args) + return + } + if assert.NoError(t, err, "Parse(%v) should succeed.", tc.args) { + assert.Equal(t, tc.wantLevel, *actual, "Level mismatch.") + } +} + +func TestLevelFlag(t *testing.T) { + tests := []flagTestCase{ + { + args: nil, + wantLevel: zapcore.InfoLevel, + }, + { + args: []string{"--level", "unknown"}, + wantErr: true, + }, + { + args: []string{"--level", "error"}, + wantLevel: zapcore.ErrorLevel, + }, + } + + for _, tt := range tests { + tt.runExplicitSet(t) + tt.runImplicitSet(t) + } +} + +func TestLevelFlagsAreIndependent(t *testing.T) { + origCommandLine := flag.CommandLine + flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError) + flag.CommandLine.SetOutput(io.Discard) + defer func() { flag.CommandLine = origCommandLine }() + + // Make sure that these two flags are independent. + fileLevel := LevelFlag("file-level", InfoLevel, "") + consoleLevel := LevelFlag("console-level", InfoLevel, "") + + assert.NoError(t, flag.CommandLine.Parse([]string{"-file-level", "debug"}), "Unexpected flag-parsing error.") + assert.Equal(t, InfoLevel, *consoleLevel, "Expected file logging level to remain unchanged.") + assert.Equal(t, DebugLevel, *fileLevel, "Expected console logging level to have changed.") +} diff --git a/vendor/go.uber.org/zap/global.go b/vendor/go.uber.org/zap/global.go new file mode 100644 index 0000000000..3cb46c9e0a --- /dev/null +++ b/vendor/go.uber.org/zap/global.go @@ -0,0 +1,169 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "bytes" + "fmt" + "log" + "os" + "sync" + + "go.uber.org/zap/zapcore" +) + +const ( + _stdLogDefaultDepth = 1 + _loggerWriterDepth = 2 + _programmerErrorTemplate = "You've found a bug in zap! Please file a bug at " + + "https://github.com/uber-go/zap/issues/new and reference this error: %v" +) + +var ( + _globalMu sync.RWMutex + _globalL = NewNop() + _globalS = _globalL.Sugar() +) + +// L returns the global Logger, which can be reconfigured with ReplaceGlobals. +// It's safe for concurrent use. +func L() *Logger { + _globalMu.RLock() + l := _globalL + _globalMu.RUnlock() + return l +} + +// S returns the global SugaredLogger, which can be reconfigured with +// ReplaceGlobals. It's safe for concurrent use. +func S() *SugaredLogger { + _globalMu.RLock() + s := _globalS + _globalMu.RUnlock() + return s +} + +// ReplaceGlobals replaces the global Logger and SugaredLogger, and returns a +// function to restore the original values. It's safe for concurrent use. +func ReplaceGlobals(logger *Logger) func() { + _globalMu.Lock() + prev := _globalL + _globalL = logger + _globalS = logger.Sugar() + _globalMu.Unlock() + return func() { ReplaceGlobals(prev) } +} + +// NewStdLog returns a *log.Logger which writes to the supplied zap Logger at +// InfoLevel. To redirect the standard library's package-global logging +// functions, use RedirectStdLog instead. +func NewStdLog(l *Logger) *log.Logger { + logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth)) + f := logger.Info + return log.New(&loggerWriter{f}, "" /* prefix */, 0 /* flags */) +} + +// NewStdLogAt returns *log.Logger which writes to supplied zap logger at +// required level. +func NewStdLogAt(l *Logger, level zapcore.Level) (*log.Logger, error) { + logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth)) + logFunc, err := levelToFunc(logger, level) + if err != nil { + return nil, err + } + return log.New(&loggerWriter{logFunc}, "" /* prefix */, 0 /* flags */), nil +} + +// RedirectStdLog redirects output from the standard library's package-global +// logger to the supplied logger at InfoLevel. Since zap already handles caller +// annotations, timestamps, etc., it automatically disables the standard +// library's annotations and prefixing. +// +// It returns a function to restore the original prefix and flags and reset the +// standard library's output to os.Stderr. +func RedirectStdLog(l *Logger) func() { + f, err := redirectStdLogAt(l, InfoLevel) + if err != nil { + // Can't get here, since passing InfoLevel to redirectStdLogAt always + // works. + panic(fmt.Sprintf(_programmerErrorTemplate, err)) + } + return f +} + +// RedirectStdLogAt redirects output from the standard library's package-global +// logger to the supplied logger at the specified level. Since zap already +// handles caller annotations, timestamps, etc., it automatically disables the +// standard library's annotations and prefixing. +// +// It returns a function to restore the original prefix and flags and reset the +// standard library's output to os.Stderr. +func RedirectStdLogAt(l *Logger, level zapcore.Level) (func(), error) { + return redirectStdLogAt(l, level) +} + +func redirectStdLogAt(l *Logger, level zapcore.Level) (func(), error) { + flags := log.Flags() + prefix := log.Prefix() + log.SetFlags(0) + log.SetPrefix("") + logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth)) + logFunc, err := levelToFunc(logger, level) + if err != nil { + return nil, err + } + log.SetOutput(&loggerWriter{logFunc}) + return func() { + log.SetFlags(flags) + log.SetPrefix(prefix) + log.SetOutput(os.Stderr) + }, nil +} + +func levelToFunc(logger *Logger, lvl zapcore.Level) (func(string, ...Field), error) { + switch lvl { + case DebugLevel: + return logger.Debug, nil + case InfoLevel: + return logger.Info, nil + case WarnLevel: + return logger.Warn, nil + case ErrorLevel: + return logger.Error, nil + case DPanicLevel: + return logger.DPanic, nil + case PanicLevel: + return logger.Panic, nil + case FatalLevel: + return logger.Fatal, nil + } + return nil, fmt.Errorf("unrecognized level: %q", lvl) +} + +type loggerWriter struct { + logFunc func(msg string, fields ...Field) +} + +func (l *loggerWriter) Write(p []byte) (int, error) { + p = bytes.TrimSpace(p) + l.logFunc(string(p)) + return len(p), nil +} diff --git a/vendor/go.uber.org/zap/global_test.go b/vendor/go.uber.org/zap/global_test.go new file mode 100644 index 0000000000..17fa225e62 --- /dev/null +++ b/vendor/go.uber.org/zap/global_test.go @@ -0,0 +1,281 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "log" + "sync" + "sync/atomic" + "testing" + "time" + + "go.uber.org/zap/internal/exit" + "go.uber.org/zap/internal/ztest" + + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestReplaceGlobals(t *testing.T) { + initialL := *L() + initialS := *S() + + withLogger(t, DebugLevel, nil, func(l *Logger, logs *observer.ObservedLogs) { + L().Info("no-op") + S().Info("no-op") + assert.Equal(t, 0, logs.Len(), "Expected initial logs to go to default no-op global.") + + defer ReplaceGlobals(l)() + + L().Info("captured") + S().Info("captured") + expected := observer.LoggedEntry{ + Entry: zapcore.Entry{Message: "captured"}, + Context: []Field{}, + } + assert.Equal( + t, + []observer.LoggedEntry{expected, expected}, + logs.AllUntimed(), + "Unexpected global log output.", + ) + }) + + assert.Equal(t, initialL, *L(), "Expected func returned from ReplaceGlobals to restore initial L.") + assert.Equal(t, initialS, *S(), "Expected func returned from ReplaceGlobals to restore initial S.") +} + +func TestGlobalsConcurrentUse(t *testing.T) { + var ( + stop atomic.Bool + wg sync.WaitGroup + ) + + for i := 0; i < 100; i++ { + wg.Add(2) + go func() { + for !stop.Load() { + ReplaceGlobals(NewNop()) + } + wg.Done() + }() + go func() { + for !stop.Load() { + L().With(Int("foo", 42)).Named("main").WithOptions(Development()).Info("") + S().Info("") + } + wg.Done() + }() + } + + ztest.Sleep(100 * time.Millisecond) + // CAS loop to toggle the current value. + for old := stop.Load(); !stop.CompareAndSwap(old, !old); { + old = stop.Load() + } + wg.Wait() +} + +func TestNewStdLog(t *testing.T) { + withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { + std := NewStdLog(l) + std.Print("redirected") + checkStdLogMessage(t, "redirected", logs) + }) +} + +func TestNewStdLogAt(t *testing.T) { + // include DPanicLevel here, but do not include Development in options + levels := []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} + for _, level := range levels { + withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { + std, err := NewStdLogAt(l, level) + require.NoError(t, err, "Unexpected error.") + std.Print("redirected") + checkStdLogMessage(t, "redirected", logs) + }) + } +} + +func TestNewStdLogAtPanics(t *testing.T) { + // include DPanicLevel here and enable Development in options + levels := []zapcore.Level{DPanicLevel, PanicLevel} + for _, level := range levels { + withLogger(t, DebugLevel, []Option{AddCaller(), Development()}, func(l *Logger, logs *observer.ObservedLogs) { + std, err := NewStdLogAt(l, level) + require.NoError(t, err, "Unexpected error") + assert.Panics(t, func() { std.Print("redirected") }, "Expected log to panic.") + checkStdLogMessage(t, "redirected", logs) + }) + } +} + +func TestNewStdLogAtFatal(t *testing.T) { + withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { + stub := exit.WithStub(func() { + std, err := NewStdLogAt(l, FatalLevel) + require.NoError(t, err, "Unexpected error.") + std.Print("redirected") + checkStdLogMessage(t, "redirected", logs) + }) + assert.True(t, true, stub.Exited, "Expected Fatal logger call to terminate process.") + stub.Unstub() + }) +} + +func TestNewStdLogAtInvalid(t *testing.T) { + _, err := NewStdLogAt(NewNop(), zapcore.Level(99)) + assert.ErrorContains(t, err, "99", "Expected level code in error message") +} + +func TestRedirectStdLog(t *testing.T) { + initialFlags := log.Flags() + initialPrefix := log.Prefix() + + withLogger(t, DebugLevel, nil, func(l *Logger, logs *observer.ObservedLogs) { + defer RedirectStdLog(l)() + log.Print("redirected") + + assert.Equal(t, []observer.LoggedEntry{{ + Entry: zapcore.Entry{Message: "redirected"}, + Context: []Field{}, + }}, logs.AllUntimed(), "Unexpected global log output.") + }) + + assert.Equal(t, initialFlags, log.Flags(), "Expected to reset initial flags.") + assert.Equal(t, initialPrefix, log.Prefix(), "Expected to reset initial prefix.") +} + +func TestRedirectStdLogCaller(t *testing.T) { + withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { + defer RedirectStdLog(l)() + log.Print("redirected") + entries := logs.All() + require.Len(t, entries, 1, "Unexpected number of logs.") + assert.Contains(t, entries[0].Caller.File, "global_test.go", "Unexpected caller annotation.") + }) +} + +func TestRedirectStdLogAt(t *testing.T) { + initialFlags := log.Flags() + initialPrefix := log.Prefix() + + // include DPanicLevel here, but do not include Development in options + levels := []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} + for _, level := range levels { + withLogger(t, DebugLevel, nil, func(l *Logger, logs *observer.ObservedLogs) { + restore, err := RedirectStdLogAt(l, level) + require.NoError(t, err, "Unexpected error.") + defer restore() + log.Print("redirected") + + assert.Equal(t, []observer.LoggedEntry{{ + Entry: zapcore.Entry{Level: level, Message: "redirected"}, + Context: []Field{}, + }}, logs.AllUntimed(), "Unexpected global log output.") + }) + } + + assert.Equal(t, initialFlags, log.Flags(), "Expected to reset initial flags.") + assert.Equal(t, initialPrefix, log.Prefix(), "Expected to reset initial prefix.") +} + +func TestRedirectStdLogAtCaller(t *testing.T) { + // include DPanicLevel here, but do not include Development in options + levels := []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} + for _, level := range levels { + withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { + restore, err := RedirectStdLogAt(l, level) + require.NoError(t, err, "Unexpected error.") + defer restore() + log.Print("redirected") + entries := logs.All() + require.Len(t, entries, 1, "Unexpected number of logs.") + assert.Contains(t, entries[0].Caller.File, "global_test.go", "Unexpected caller annotation.") + }) + } +} + +func TestRedirectStdLogAtPanics(t *testing.T) { + initialFlags := log.Flags() + initialPrefix := log.Prefix() + + // include DPanicLevel here and enable Development in options + levels := []zapcore.Level{DPanicLevel, PanicLevel} + for _, level := range levels { + withLogger(t, DebugLevel, []Option{AddCaller(), Development()}, func(l *Logger, logs *observer.ObservedLogs) { + restore, err := RedirectStdLogAt(l, level) + require.NoError(t, err, "Unexpected error.") + defer restore() + assert.Panics(t, func() { log.Print("redirected") }, "Expected log to panic.") + checkStdLogMessage(t, "redirected", logs) + }) + } + + assert.Equal(t, initialFlags, log.Flags(), "Expected to reset initial flags.") + assert.Equal(t, initialPrefix, log.Prefix(), "Expected to reset initial prefix.") +} + +func TestRedirectStdLogAtFatal(t *testing.T) { + initialFlags := log.Flags() + initialPrefix := log.Prefix() + + withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { + stub := exit.WithStub(func() { + restore, err := RedirectStdLogAt(l, FatalLevel) + require.NoError(t, err, "Unexpected error.") + defer restore() + log.Print("redirected") + checkStdLogMessage(t, "redirected", logs) + }) + assert.True(t, true, stub.Exited, "Expected Fatal logger call to terminate process.") + stub.Unstub() + }) + + assert.Equal(t, initialFlags, log.Flags(), "Expected to reset initial flags.") + assert.Equal(t, initialPrefix, log.Prefix(), "Expected to reset initial prefix.") +} + +func TestRedirectStdLogAtInvalid(t *testing.T) { + restore, err := RedirectStdLogAt(NewNop(), zapcore.Level(99)) + defer func() { + if restore != nil { + restore() + } + }() + assert.ErrorContains(t, err, "99", "Expected level code in error message") +} + +func checkStdLogMessage(t *testing.T, msg string, logs *observer.ObservedLogs) { + require.Equal(t, 1, logs.Len(), "Expected exactly one entry to be logged") + entry := logs.AllUntimed()[0] + assert.Equal(t, []Field{}, entry.Context, "Unexpected entry context.") + assert.Equal(t, "redirected", entry.Message, "Unexpected entry message.") + assert.Regexp( + t, + `/global_test.go:\d+$`, + entry.Caller.String(), + "Unexpected caller annotation.", + ) +} diff --git a/vendor/go.uber.org/zap/gotest/ya.make b/vendor/go.uber.org/zap/gotest/ya.make new file mode 100644 index 0000000000..7e4cd8d0f4 --- /dev/null +++ b/vendor/go.uber.org/zap/gotest/ya.make @@ -0,0 +1,5 @@ +GO_TEST_FOR(vendor/go.uber.org/zap) + +LICENSE(MIT) + +END() diff --git a/vendor/go.uber.org/zap/http_handler.go b/vendor/go.uber.org/zap/http_handler.go new file mode 100644 index 0000000000..2be8f65150 --- /dev/null +++ b/vendor/go.uber.org/zap/http_handler.go @@ -0,0 +1,140 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "go.uber.org/zap/zapcore" +) + +// ServeHTTP is a simple JSON endpoint that can report on or change the current +// logging level. +// +// # GET +// +// The GET request returns a JSON description of the current logging level like: +// +// {"level":"info"} +// +// # PUT +// +// The PUT request changes the logging level. It is perfectly safe to change the +// logging level while a program is running. Two content types are supported: +// +// Content-Type: application/x-www-form-urlencoded +// +// With this content type, the level can be provided through the request body or +// a query parameter. The log level is URL encoded like: +// +// level=debug +// +// The request body takes precedence over the query parameter, if both are +// specified. +// +// This content type is the default for a curl PUT request. Following are two +// example curl requests that both set the logging level to debug. +// +// curl -X PUT localhost:8080/log/level?level=debug +// curl -X PUT localhost:8080/log/level -d level=debug +// +// For any other content type, the payload is expected to be JSON encoded and +// look like: +// +// {"level":"info"} +// +// An example curl request could look like this: +// +// curl -X PUT localhost:8080/log/level -H "Content-Type: application/json" -d '{"level":"debug"}' +func (lvl AtomicLevel) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if err := lvl.serveHTTP(w, r); err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "internal error: %v", err) + } +} + +func (lvl AtomicLevel) serveHTTP(w http.ResponseWriter, r *http.Request) error { + type errorResponse struct { + Error string `json:"error"` + } + type payload struct { + Level zapcore.Level `json:"level"` + } + + enc := json.NewEncoder(w) + + switch r.Method { + case http.MethodGet: + return enc.Encode(payload{Level: lvl.Level()}) + + case http.MethodPut: + requestedLvl, err := decodePutRequest(r.Header.Get("Content-Type"), r) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return enc.Encode(errorResponse{Error: err.Error()}) + } + lvl.SetLevel(requestedLvl) + return enc.Encode(payload{Level: lvl.Level()}) + + default: + w.WriteHeader(http.StatusMethodNotAllowed) + return enc.Encode(errorResponse{ + Error: "Only GET and PUT are supported.", + }) + } +} + +// Decodes incoming PUT requests and returns the requested logging level. +func decodePutRequest(contentType string, r *http.Request) (zapcore.Level, error) { + if contentType == "application/x-www-form-urlencoded" { + return decodePutURL(r) + } + return decodePutJSON(r.Body) +} + +func decodePutURL(r *http.Request) (zapcore.Level, error) { + lvl := r.FormValue("level") + if lvl == "" { + return 0, errors.New("must specify logging level") + } + var l zapcore.Level + if err := l.UnmarshalText([]byte(lvl)); err != nil { + return 0, err + } + return l, nil +} + +func decodePutJSON(body io.Reader) (zapcore.Level, error) { + var pld struct { + Level *zapcore.Level `json:"level"` + } + if err := json.NewDecoder(body).Decode(&pld); err != nil { + return 0, fmt.Errorf("malformed request body: %v", err) + } + if pld.Level == nil { + return 0, errors.New("must specify logging level") + } + return *pld.Level, nil +} diff --git a/vendor/go.uber.org/zap/http_handler_test.go b/vendor/go.uber.org/zap/http_handler_test.go new file mode 100644 index 0000000000..9da3dc7b52 --- /dev/null +++ b/vendor/go.uber.org/zap/http_handler_test.go @@ -0,0 +1,217 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap_test + +import ( + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAtomicLevelServeHTTP(t *testing.T) { + tests := []struct { + desc string + method string + query string + contentType string + body string + expectedCode int + expectedLevel zapcore.Level + }{ + { + desc: "GET", + method: http.MethodGet, + expectedCode: http.StatusOK, + expectedLevel: zap.InfoLevel, + }, + { + desc: "PUT JSON", + method: http.MethodPut, + expectedCode: http.StatusOK, + expectedLevel: zap.WarnLevel, + body: `{"level":"warn"}`, + }, + { + desc: "PUT URL encoded", + method: http.MethodPut, + expectedCode: http.StatusOK, + expectedLevel: zap.WarnLevel, + contentType: "application/x-www-form-urlencoded", + body: "level=warn", + }, + { + desc: "PUT query parameters", + method: http.MethodPut, + query: "?level=warn", + expectedCode: http.StatusOK, + expectedLevel: zap.WarnLevel, + contentType: "application/x-www-form-urlencoded", + }, + { + desc: "body takes precedence over query", + method: http.MethodPut, + query: "?level=info", + expectedCode: http.StatusOK, + expectedLevel: zap.WarnLevel, + contentType: "application/x-www-form-urlencoded", + body: "level=warn", + }, + { + desc: "JSON ignores query", + method: http.MethodPut, + query: "?level=info", + expectedCode: http.StatusOK, + expectedLevel: zap.WarnLevel, + body: `{"level":"warn"}`, + }, + { + desc: "PUT JSON unrecognized", + method: http.MethodPut, + expectedCode: http.StatusBadRequest, + body: `{"level":"unrecognized"}`, + }, + { + desc: "PUT URL encoded unrecognized", + method: http.MethodPut, + expectedCode: http.StatusBadRequest, + contentType: "application/x-www-form-urlencoded", + body: "level=unrecognized", + }, + { + desc: "PUT JSON malformed", + method: http.MethodPut, + expectedCode: http.StatusBadRequest, + body: `{"level":"warn`, + }, + { + desc: "PUT URL encoded malformed", + method: http.MethodPut, + query: "?level=%", + expectedCode: http.StatusBadRequest, + contentType: "application/x-www-form-urlencoded", + }, + { + desc: "PUT Query parameters malformed", + method: http.MethodPut, + expectedCode: http.StatusBadRequest, + contentType: "application/x-www-form-urlencoded", + body: "level=%", + }, + { + desc: "PUT JSON unspecified", + method: http.MethodPut, + expectedCode: http.StatusBadRequest, + body: `{}`, + }, + { + desc: "PUT URL encoded unspecified", + method: http.MethodPut, + expectedCode: http.StatusBadRequest, + contentType: "application/x-www-form-urlencoded", + body: "", + }, + { + desc: "POST JSON", + method: http.MethodPost, + expectedCode: http.StatusMethodNotAllowed, + body: `{"level":"warn"}`, + }, + { + desc: "POST URL", + method: http.MethodPost, + expectedCode: http.StatusMethodNotAllowed, + contentType: "application/x-www-form-urlencoded", + body: "level=warn", + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + lvl := zap.NewAtomicLevel() + lvl.SetLevel(zapcore.InfoLevel) + + server := httptest.NewServer(lvl) + defer server.Close() + + req, err := http.NewRequest(tt.method, server.URL+tt.query, strings.NewReader(tt.body)) + require.NoError(t, err, "Error constructing %s request.", req.Method) + if tt.contentType != "" { + req.Header.Set("Content-Type", tt.contentType) + } + + res, err := http.DefaultClient.Do(req) + require.NoError(t, err, "Error making %s request.", req.Method) + defer func() { + assert.NoError(t, res.Body.Close(), "Error closing response body.") + }() + + require.Equal(t, tt.expectedCode, res.StatusCode, "Unexpected status code.") + if tt.expectedCode != http.StatusOK { + // Don't need to test exact error message, but one should be present. + var pld struct { + Error string `json:"error"` + } + require.NoError(t, json.NewDecoder(res.Body).Decode(&pld), "Decoding response body") + assert.NotEmpty(t, pld.Error, "Expected an error message") + return + } + + var pld struct { + Level zapcore.Level `json:"level"` + } + require.NoError(t, json.NewDecoder(res.Body).Decode(&pld), "Decoding response body") + assert.Equal(t, tt.expectedLevel, pld.Level, "Unexpected logging level returned") + }) + } +} + +func TestAtomicLevelServeHTTPBrokenWriter(t *testing.T) { + t.Parallel() + + lvl := zap.NewAtomicLevel() + + request, err := http.NewRequest(http.MethodGet, "http://localhost:1234/log/level", nil) + require.NoError(t, err, "Error constructing request.") + + recorder := httptest.NewRecorder() + lvl.ServeHTTP(&brokenHTTPResponseWriter{ + ResponseWriter: recorder, + }, request) + + assert.Equal(t, http.StatusInternalServerError, recorder.Code, "Unexpected status code.") +} + +type brokenHTTPResponseWriter struct { + http.ResponseWriter +} + +func (w *brokenHTTPResponseWriter) Write([]byte) (int, error) { + return 0, errors.New("great sadness") +} diff --git a/vendor/go.uber.org/zap/increase_level_test.go b/vendor/go.uber.org/zap/increase_level_test.go new file mode 100644 index 0000000000..2d883807e9 --- /dev/null +++ b/vendor/go.uber.org/zap/increase_level_test.go @@ -0,0 +1,94 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" +) + +func newLoggedEntry(level zapcore.Level, msg string, fields ...zapcore.Field) observer.LoggedEntry { + if len(fields) == 0 { + fields = []zapcore.Field{} + } + return observer.LoggedEntry{ + Entry: zapcore.Entry{Level: level, Message: msg}, + Context: fields, + } +} + +func TestIncreaseLevelTryDecrease(t *testing.T) { + errorOut := &bytes.Buffer{} + opts := []Option{ + ErrorOutput(zapcore.AddSync(errorOut)), + } + withLogger(t, WarnLevel, opts, func(logger *Logger, logs *observer.ObservedLogs) { + logger.Warn("original warn log") + + debugLogger := logger.WithOptions(IncreaseLevel(DebugLevel)) + debugLogger.Debug("ignored debug log") + debugLogger.Warn("increase level warn log") + debugLogger.Error("increase level error log") + + assert.Equal(t, []observer.LoggedEntry{ + newLoggedEntry(WarnLevel, "original warn log"), + newLoggedEntry(WarnLevel, "increase level warn log"), + newLoggedEntry(ErrorLevel, "increase level error log"), + }, logs.AllUntimed(), "unexpected logs") + assert.Equal(t, + "failed to IncreaseLevel: invalid increase level, as level \"info\" is allowed by increased level, but not by existing core\n", + errorOut.String(), + "unexpected error output", + ) + }) +} + +func TestIncreaseLevel(t *testing.T) { + errorOut := &bytes.Buffer{} + opts := []Option{ + ErrorOutput(zapcore.AddSync(errorOut)), + } + withLogger(t, WarnLevel, opts, func(logger *Logger, logs *observer.ObservedLogs) { + logger.Warn("original warn log") + + errorLogger := logger.WithOptions(IncreaseLevel(ErrorLevel)) + errorLogger.Debug("ignored debug log") + errorLogger.Warn("ignored warn log") + errorLogger.Error("increase level error log") + + withFields := errorLogger.With(String("k", "v")) + withFields.Debug("ignored debug log with fields") + withFields.Warn("ignored warn log with fields") + withFields.Error("increase level error log with fields") + + assert.Equal(t, []observer.LoggedEntry{ + newLoggedEntry(WarnLevel, "original warn log"), + newLoggedEntry(ErrorLevel, "increase level error log"), + newLoggedEntry(ErrorLevel, "increase level error log with fields", String("k", "v")), + }, logs.AllUntimed(), "unexpected logs") + + assert.Empty(t, errorOut.String(), "expect no error output") + }) +} diff --git a/vendor/go.uber.org/zap/internal/bufferpool/bufferpool.go b/vendor/go.uber.org/zap/internal/bufferpool/bufferpool.go new file mode 100644 index 0000000000..dad583aaa5 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/bufferpool/bufferpool.go @@ -0,0 +1,31 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package bufferpool houses zap's shared internal buffer pool. Third-party +// packages can recreate the same functionality with buffers.NewPool. +package bufferpool + +import "go.uber.org/zap/buffer" + +var ( + _pool = buffer.NewPool() + // Get retrieves a buffer from the pool, creating one if necessary. + Get = _pool.Get +) diff --git a/vendor/go.uber.org/zap/internal/bufferpool/ya.make b/vendor/go.uber.org/zap/internal/bufferpool/ya.make new file mode 100644 index 0000000000..1ab106c924 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/bufferpool/ya.make @@ -0,0 +1,9 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + bufferpool.go +) + +END() diff --git a/vendor/go.uber.org/zap/internal/color/color.go b/vendor/go.uber.org/zap/internal/color/color.go new file mode 100644 index 0000000000..c4d5d02abc --- /dev/null +++ b/vendor/go.uber.org/zap/internal/color/color.go @@ -0,0 +1,44 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package color adds coloring functionality for TTY output. +package color + +import "fmt" + +// Foreground colors. +const ( + Black Color = iota + 30 + Red + Green + Yellow + Blue + Magenta + Cyan + White +) + +// Color represents a text color. +type Color uint8 + +// Add adds the coloring to the given string. +func (c Color) Add(s string) string { + return fmt.Sprintf("\x1b[%dm%s\x1b[0m", uint8(c), s) +} diff --git a/vendor/go.uber.org/zap/internal/color/color_test.go b/vendor/go.uber.org/zap/internal/color/color_test.go new file mode 100644 index 0000000000..4982903aa1 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/color/color_test.go @@ -0,0 +1,36 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package color + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestColorFormatting(t *testing.T) { + assert.Equal( + t, + "\x1b[31mfoo\x1b[0m", + Red.Add("foo"), + "Unexpected colored output.", + ) +} diff --git a/vendor/go.uber.org/zap/internal/color/gotest/ya.make b/vendor/go.uber.org/zap/internal/color/gotest/ya.make new file mode 100644 index 0000000000..32cf4e2849 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/color/gotest/ya.make @@ -0,0 +1,5 @@ +GO_TEST_FOR(vendor/go.uber.org/zap/internal/color) + +LICENSE(MIT) + +END() diff --git a/vendor/go.uber.org/zap/internal/color/ya.make b/vendor/go.uber.org/zap/internal/color/ya.make new file mode 100644 index 0000000000..3e9fbfcd47 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/color/ya.make @@ -0,0 +1,15 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + color.go +) + +GO_TEST_SRCS(color_test.go) + +END() + +RECURSE( + gotest +) diff --git a/vendor/go.uber.org/zap/internal/exit/exit.go b/vendor/go.uber.org/zap/internal/exit/exit.go new file mode 100644 index 0000000000..f673f9947b --- /dev/null +++ b/vendor/go.uber.org/zap/internal/exit/exit.go @@ -0,0 +1,66 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package exit provides stubs so that unit tests can exercise code that calls +// os.Exit(1). +package exit + +import "os" + +var _exit = os.Exit + +// With terminates the process by calling os.Exit(code). If the package is +// stubbed, it instead records a call in the testing spy. +func With(code int) { + _exit(code) +} + +// A StubbedExit is a testing fake for os.Exit. +type StubbedExit struct { + Exited bool + Code int + prev func(code int) +} + +// Stub substitutes a fake for the call to os.Exit(1). +func Stub() *StubbedExit { + s := &StubbedExit{prev: _exit} + _exit = s.exit + return s +} + +// WithStub runs the supplied function with Exit stubbed. It returns the stub +// used, so that users can test whether the process would have crashed. +func WithStub(f func()) *StubbedExit { + s := Stub() + defer s.Unstub() + f() + return s +} + +// Unstub restores the previous exit function. +func (se *StubbedExit) Unstub() { + _exit = se.prev +} + +func (se *StubbedExit) exit(code int) { + se.Exited = true + se.Code = code +} diff --git a/vendor/go.uber.org/zap/internal/exit/exit_test.go b/vendor/go.uber.org/zap/internal/exit/exit_test.go new file mode 100644 index 0000000000..2299584722 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/exit/exit_test.go @@ -0,0 +1,48 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package exit_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap/internal/exit" +) + +func TestStub(t *testing.T) { + type want struct { + exit bool + code int + } + tests := []struct { + f func() + want want + }{ + {func() { exit.With(42) }, want{exit: true, code: 42}}, + {func() {}, want{}}, + } + + for _, tt := range tests { + s := exit.WithStub(tt.f) + assert.Equal(t, tt.want.exit, s.Exited, "Stub captured unexpected exit value.") + assert.Equal(t, tt.want.code, s.Code, "Stub captured unexpected exit value.") + } +} diff --git a/vendor/go.uber.org/zap/internal/exit/gotest/ya.make b/vendor/go.uber.org/zap/internal/exit/gotest/ya.make new file mode 100644 index 0000000000..c5e8098769 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/exit/gotest/ya.make @@ -0,0 +1,5 @@ +GO_TEST_FOR(vendor/go.uber.org/zap/internal/exit) + +LICENSE(MIT) + +END() diff --git a/vendor/go.uber.org/zap/internal/exit/ya.make b/vendor/go.uber.org/zap/internal/exit/ya.make new file mode 100644 index 0000000000..73d0dd8e21 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/exit/ya.make @@ -0,0 +1,15 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + exit.go +) + +GO_XTEST_SRCS(exit_test.go) + +END() + +RECURSE( + gotest +) diff --git a/vendor/go.uber.org/zap/internal/level_enabler.go b/vendor/go.uber.org/zap/internal/level_enabler.go new file mode 100644 index 0000000000..40bfed81e6 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/level_enabler.go @@ -0,0 +1,37 @@ +// Copyright (c) 2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package internal and its subpackages hold types and functionality +// that are not part of Zap's public API. +package internal + +import "go.uber.org/zap/zapcore" + +// LeveledEnabler is an interface satisfied by LevelEnablers that are able to +// report their own level. +// +// This interface is defined to use more conveniently in tests and non-zapcore +// packages. +// This cannot be imported from zapcore because of the cyclic dependency. +type LeveledEnabler interface { + zapcore.LevelEnabler + + Level() zapcore.Level +} diff --git a/vendor/go.uber.org/zap/internal/pool/gotest/ya.make b/vendor/go.uber.org/zap/internal/pool/gotest/ya.make new file mode 100644 index 0000000000..8e1e7117ab --- /dev/null +++ b/vendor/go.uber.org/zap/internal/pool/gotest/ya.make @@ -0,0 +1,5 @@ +GO_TEST_FOR(vendor/go.uber.org/zap/internal/pool) + +LICENSE(MIT) + +END() diff --git a/vendor/go.uber.org/zap/internal/pool/pool.go b/vendor/go.uber.org/zap/internal/pool/pool.go new file mode 100644 index 0000000000..60e9d2c432 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/pool/pool.go @@ -0,0 +1,58 @@ +// Copyright (c) 2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package pool provides internal pool utilities. +package pool + +import ( + "sync" +) + +// A Pool is a generic wrapper around [sync.Pool] to provide strongly-typed +// object pooling. +// +// Note that SA6002 (ref: https://staticcheck.io/docs/checks/#SA6002) will +// not be detected, so all internal pool use must take care to only store +// pointer types. +type Pool[T any] struct { + pool sync.Pool +} + +// New returns a new [Pool] for T, and will use fn to construct new Ts when +// the pool is empty. +func New[T any](fn func() T) *Pool[T] { + return &Pool[T]{ + pool: sync.Pool{ + New: func() any { + return fn() + }, + }, + } +} + +// Get gets a T from the pool, or creates a new one if the pool is empty. +func (p *Pool[T]) Get() T { + return p.pool.Get().(T) +} + +// Put returns x into the pool. +func (p *Pool[T]) Put(x T) { + p.pool.Put(x) +} diff --git a/vendor/go.uber.org/zap/internal/pool/pool_test.go b/vendor/go.uber.org/zap/internal/pool/pool_test.go new file mode 100644 index 0000000000..094edf9172 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/pool/pool_test.go @@ -0,0 +1,106 @@ +// Copyright (c) 2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package pool_test + +import ( + "runtime/debug" + "sync" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/zap/internal/pool" +) + +type pooledValue[T any] struct { + value T +} + +func TestNew(t *testing.T) { + // Disable GC to avoid the victim cache during the test. + defer debug.SetGCPercent(debug.SetGCPercent(-1)) + + p := pool.New(func() *pooledValue[string] { + return &pooledValue[string]{ + value: "new", + } + }) + + // Probabilistically, 75% of sync.Pool.Put calls will succeed when -race + // is enabled (see ref below); attempt to make this quasi-deterministic by + // brute force (i.e., put significantly more objects in the pool than we + // will need for the test) in order to avoid testing without race enabled. + // + // ref: https://cs.opensource.google/go/go/+/refs/tags/go1.20.2:src/sync/pool.go;l=100-103 + for i := 0; i < 1_000; i++ { + p.Put(&pooledValue[string]{ + value: t.Name(), + }) + } + + // Ensure that we always get the expected value. Note that this must only + // run a fraction of the number of times that Put is called above. + for i := 0; i < 10; i++ { + func() { + x := p.Get() + defer p.Put(x) + require.Equal(t, t.Name(), x.value) + }() + } + + // Depool all objects that might be in the pool to ensure that it's empty. + for i := 0; i < 1_000; i++ { + p.Get() + } + + // Now that the pool is empty, it should use the value specified in the + // underlying sync.Pool.New func. + require.Equal(t, "new", p.Get().value) +} + +func TestNew_Race(t *testing.T) { + p := pool.New(func() *pooledValue[int] { + return &pooledValue[int]{ + value: -1, + } + }) + + var wg sync.WaitGroup + defer wg.Wait() + + // Run a number of goroutines that read and write pool object fields to + // tease out races. + for i := 0; i < 1_000; i++ { + i := i + + wg.Add(1) + go func() { + defer wg.Done() + + x := p.Get() + defer p.Put(x) + + // Must both read and write the field. + if n := x.value; n >= -1 { + x.value = i + } + }() + } +} diff --git a/vendor/go.uber.org/zap/internal/pool/ya.make b/vendor/go.uber.org/zap/internal/pool/ya.make new file mode 100644 index 0000000000..cd2e0b4ab2 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/pool/ya.make @@ -0,0 +1,15 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + pool.go +) + +GO_XTEST_SRCS(pool_test.go) + +END() + +RECURSE( + gotest +) diff --git a/vendor/go.uber.org/zap/internal/readme/readme.go b/vendor/go.uber.org/zap/internal/readme/readme.go new file mode 100644 index 0000000000..148765908b --- /dev/null +++ b/vendor/go.uber.org/zap/internal/readme/readme.go @@ -0,0 +1,244 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// readme generates Zap's README from a template. +package main + +import ( + "flag" + "fmt" + "io" + "log" + "os" + "os/exec" + "sort" + "strconv" + "strings" + "text/template" + "time" +) + +var ( + libraryNameToMarkdownName = map[string]string{ + "Zap": ":zap: zap", + "Zap.Sugar": ":zap: zap (sugared)", + "stdlib.Println": "standard library", + "sirupsen/logrus": "logrus", + "go-kit/kit/log": "go-kit", + "inconshreveable/log15": "log15", + "apex/log": "apex/log", + "rs/zerolog": "zerolog", + "slog": "slog", + } +) + +func main() { + flag.Parse() + if err := do(); err != nil { + log.Fatal(err) + } +} + +func do() error { + tmplData, err := getTmplData() + if err != nil { + return err + } + data, err := io.ReadAll(os.Stdin) + if err != nil { + return err + } + t, err := template.New("tmpl").Parse(string(data)) + if err != nil { + return err + } + return t.Execute(os.Stdout, tmplData) +} + +func getTmplData() (*tmplData, error) { + tmplData := &tmplData{} + rows, err := getBenchmarkRows("BenchmarkAddingFields") + if err != nil { + return nil, err + } + tmplData.BenchmarkAddingFields = rows + rows, err = getBenchmarkRows("BenchmarkAccumulatedContext") + if err != nil { + return nil, err + } + tmplData.BenchmarkAccumulatedContext = rows + rows, err = getBenchmarkRows("BenchmarkWithoutFields") + if err != nil { + return nil, err + } + tmplData.BenchmarkWithoutFields = rows + return tmplData, nil +} + +func getBenchmarkRows(benchmarkName string) (string, error) { + benchmarkOutput, err := getBenchmarkOutput(benchmarkName) + if err != nil { + return "", err + } + + // get the Zap time (unsugared) as baseline to compare with other loggers + baseline, err := getBenchmarkRow(benchmarkOutput, benchmarkName, "Zap", nil) + if err != nil { + return "", err + } + + var benchmarkRows []*benchmarkRow + for libraryName := range libraryNameToMarkdownName { + benchmarkRow, err := getBenchmarkRow( + benchmarkOutput, benchmarkName, libraryName, baseline, + ) + if err != nil { + return "", err + } + if benchmarkRow == nil { + continue + } + benchmarkRows = append(benchmarkRows, benchmarkRow) + } + sort.Sort(benchmarkRowsByTime(benchmarkRows)) + rows := []string{ + "| Package | Time | Time % to zap | Objects Allocated |", + "| :------ | :--: | :-----------: | :---------------: |", + } + for _, benchmarkRow := range benchmarkRows { + rows = append(rows, benchmarkRow.String()) + } + return strings.Join(rows, "\n"), nil +} + +func getBenchmarkRow( + input []string, benchmarkName string, libraryName string, baseline *benchmarkRow, +) (*benchmarkRow, error) { + line, err := findUniqueSubstring(input, fmt.Sprintf("%s/%s-", benchmarkName, libraryName)) + if err != nil { + return nil, err + } + if line == "" { + return nil, nil + } + split := strings.Split(line, "\t") + if len(split) < 5 { + return nil, fmt.Errorf("unknown benchmark line: %s", line) + } + duration, err := time.ParseDuration(strings.ReplaceAll(strings.TrimSuffix(strings.TrimSpace(split[2]), "/op"), " ", "")) + if err != nil { + return nil, err + } + allocatedBytes, err := strconv.Atoi(strings.TrimSuffix(strings.TrimSpace(split[3]), " B/op")) + if err != nil { + return nil, err + } + allocatedObjects, err := strconv.Atoi(strings.TrimSuffix(strings.TrimSpace(split[4]), " allocs/op")) + if err != nil { + return nil, err + } + r := &benchmarkRow{ + Name: libraryNameToMarkdownName[libraryName], + Time: duration, + AllocatedBytes: allocatedBytes, + AllocatedObjects: allocatedObjects, + } + + if baseline != nil { + r.ZapTime = baseline.Time + r.ZapAllocatedBytes = baseline.AllocatedBytes + r.ZapAllocatedObjects = baseline.AllocatedObjects + } + + return r, nil +} + +func findUniqueSubstring(input []string, substring string) (string, error) { + var output string + for _, line := range input { + if strings.Contains(line, substring) { + if output != "" { + return "", fmt.Errorf("input has duplicate substring %s", substring) + } + output = line + } + } + return output, nil +} + +func getBenchmarkOutput(benchmarkName string) ([]string, error) { + cmd := exec.Command("go", "test", fmt.Sprintf("-bench=%s", benchmarkName), "-benchmem") + cmd.Dir = "benchmarks" + output, err := cmd.CombinedOutput() + if err != nil { + return nil, fmt.Errorf("error running 'go test -bench=%q': %v\n%s", benchmarkName, err, string(output)) + } + return strings.Split(string(output), "\n"), nil +} + +type tmplData struct { + BenchmarkAddingFields string + BenchmarkAccumulatedContext string + BenchmarkWithoutFields string +} + +type benchmarkRow struct { + Name string + + Time time.Duration + AllocatedBytes int + AllocatedObjects int + + ZapTime time.Duration + ZapAllocatedBytes int + ZapAllocatedObjects int +} + +func (b *benchmarkRow) String() string { + pct := func(val, baseline int64) string { + return fmt.Sprintf( + "%+0.f%%", + ((float64(val)/float64(baseline))*100)-100, + ) + } + t := b.Time.Nanoseconds() + tp := pct(t, b.ZapTime.Nanoseconds()) + + return fmt.Sprintf( + "| %s | %d ns/op | %s | %d allocs/op", b.Name, + t, tp, b.AllocatedObjects, + ) +} + +type benchmarkRowsByTime []*benchmarkRow + +func (b benchmarkRowsByTime) Len() int { return len(b) } +func (b benchmarkRowsByTime) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b benchmarkRowsByTime) Less(i, j int) bool { + left, right := b[i], b[j] + leftZap, rightZap := strings.Contains(left.Name, "zap"), strings.Contains(right.Name, "zap") + + // If neither benchmark is for zap or both are, sort by time. + if leftZap == rightZap { + return left.Time.Nanoseconds() < right.Time.Nanoseconds() + } + // Sort zap benchmark first. + return leftZap +} diff --git a/vendor/go.uber.org/zap/internal/readme/ya.make b/vendor/go.uber.org/zap/internal/readme/ya.make new file mode 100644 index 0000000000..69bb063cfb --- /dev/null +++ b/vendor/go.uber.org/zap/internal/readme/ya.make @@ -0,0 +1,9 @@ +GO_PROGRAM() + +LICENSE(MIT) + +SRCS( + readme.go +) + +END() diff --git a/vendor/go.uber.org/zap/internal/stacktrace/gotest/ya.make b/vendor/go.uber.org/zap/internal/stacktrace/gotest/ya.make new file mode 100644 index 0000000000..e5045319ca --- /dev/null +++ b/vendor/go.uber.org/zap/internal/stacktrace/gotest/ya.make @@ -0,0 +1,5 @@ +GO_TEST_FOR(vendor/go.uber.org/zap/internal/stacktrace) + +LICENSE(MIT) + +END() diff --git a/vendor/go.uber.org/zap/internal/stacktrace/stack.go b/vendor/go.uber.org/zap/internal/stacktrace/stack.go new file mode 100644 index 0000000000..82af7551f9 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/stacktrace/stack.go @@ -0,0 +1,181 @@ +// Copyright (c) 2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package stacktrace provides support for gathering stack traces +// efficiently. +package stacktrace + +import ( + "runtime" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/internal/bufferpool" + "go.uber.org/zap/internal/pool" +) + +var _stackPool = pool.New(func() *Stack { + return &Stack{ + storage: make([]uintptr, 64), + } +}) + +// Stack is a captured stack trace. +type Stack struct { + pcs []uintptr // program counters; always a subslice of storage + frames *runtime.Frames + + // The size of pcs varies depending on requirements: + // it will be one if the only the first frame was requested, + // and otherwise it will reflect the depth of the call stack. + // + // storage decouples the slice we need (pcs) from the slice we pool. + // We will always allocate a reasonably large storage, but we'll use + // only as much of it as we need. + storage []uintptr +} + +// Depth specifies how deep of a stack trace should be captured. +type Depth int + +const ( + // First captures only the first frame. + First Depth = iota + + // Full captures the entire call stack, allocating more + // storage for it if needed. + Full +) + +// Capture captures a stack trace of the specified depth, skipping +// the provided number of frames. skip=0 identifies the caller of +// Capture. +// +// The caller must call Free on the returned stacktrace after using it. +func Capture(skip int, depth Depth) *Stack { + stack := _stackPool.Get() + + switch depth { + case First: + stack.pcs = stack.storage[:1] + case Full: + stack.pcs = stack.storage + } + + // Unlike other "skip"-based APIs, skip=0 identifies runtime.Callers + // itself. +2 to skip captureStacktrace and runtime.Callers. + numFrames := runtime.Callers( + skip+2, + stack.pcs, + ) + + // runtime.Callers truncates the recorded stacktrace if there is no + // room in the provided slice. For the full stack trace, keep expanding + // storage until there are fewer frames than there is room. + if depth == Full { + pcs := stack.pcs + for numFrames == len(pcs) { + pcs = make([]uintptr, len(pcs)*2) + numFrames = runtime.Callers(skip+2, pcs) + } + + // Discard old storage instead of returning it to the pool. + // This will adjust the pool size over time if stack traces are + // consistently very deep. + stack.storage = pcs + stack.pcs = pcs[:numFrames] + } else { + stack.pcs = stack.pcs[:numFrames] + } + + stack.frames = runtime.CallersFrames(stack.pcs) + return stack +} + +// Free releases resources associated with this stacktrace +// and returns it back to the pool. +func (st *Stack) Free() { + st.frames = nil + st.pcs = nil + _stackPool.Put(st) +} + +// Count reports the total number of frames in this stacktrace. +// Count DOES NOT change as Next is called. +func (st *Stack) Count() int { + return len(st.pcs) +} + +// Next returns the next frame in the stack trace, +// and a boolean indicating whether there are more after it. +func (st *Stack) Next() (_ runtime.Frame, more bool) { + return st.frames.Next() +} + +// Take returns a string representation of the current stacktrace. +// +// skip is the number of frames to skip before recording the stack trace. +// skip=0 identifies the caller of Take. +func Take(skip int) string { + stack := Capture(skip+1, Full) + defer stack.Free() + + buffer := bufferpool.Get() + defer buffer.Free() + + stackfmt := NewFormatter(buffer) + stackfmt.FormatStack(stack) + return buffer.String() +} + +// Formatter formats a stack trace into a readable string representation. +type Formatter struct { + b *buffer.Buffer + nonEmpty bool // whehther we've written at least one frame already +} + +// NewFormatter builds a new Formatter. +func NewFormatter(b *buffer.Buffer) Formatter { + return Formatter{b: b} +} + +// FormatStack formats all remaining frames in the provided stacktrace -- minus +// the final runtime.main/runtime.goexit frame. +func (sf *Formatter) FormatStack(stack *Stack) { + // Note: On the last iteration, frames.Next() returns false, with a valid + // frame, but we ignore this frame. The last frame is a runtime frame which + // adds noise, since it's only either runtime.main or runtime.goexit. + for frame, more := stack.Next(); more; frame, more = stack.Next() { + sf.FormatFrame(frame) + } +} + +// FormatFrame formats the given frame. +func (sf *Formatter) FormatFrame(frame runtime.Frame) { + if sf.nonEmpty { + sf.b.AppendByte('\n') + } + sf.nonEmpty = true + sf.b.AppendString(frame.Function) + sf.b.AppendByte('\n') + sf.b.AppendByte('\t') + sf.b.AppendString(frame.File) + sf.b.AppendByte(':') + sf.b.AppendInt(int64(frame.Line)) +} diff --git a/vendor/go.uber.org/zap/internal/stacktrace/stack_test.go b/vendor/go.uber.org/zap/internal/stacktrace/stack_test.go new file mode 100644 index 0000000000..195eeaeaee --- /dev/null +++ b/vendor/go.uber.org/zap/internal/stacktrace/stack_test.go @@ -0,0 +1,106 @@ +// Copyright (c) 2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package stacktrace + +import ( + "bytes" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTake(t *testing.T) { + trace := Take(0) + lines := strings.Split(trace, "\n") + require.NotEmpty(t, lines, "Expected stacktrace to have at least one frame.") + assert.Contains( + t, + lines[0], + "go.uber.org/zap/internal/stacktrace.TestTake", + "Expected stacktrace to start with the test.", + ) +} + +func TestTakeWithSkip(t *testing.T) { + trace := Take(1) + lines := strings.Split(trace, "\n") + require.NotEmpty(t, lines, "Expected stacktrace to have at least one frame.") + assert.Contains( + t, + lines[0], + "testing.", + "Expected stacktrace to start with the test runner (skipping our own frame).", + ) +} + +func TestTakeWithSkipInnerFunc(t *testing.T) { + var trace string + func() { + trace = Take(2) + }() + lines := strings.Split(trace, "\n") + require.NotEmpty(t, lines, "Expected stacktrace to have at least one frame.") + assert.Contains( + t, + lines[0], + "testing.", + "Expected stacktrace to start with the test function (skipping the test function).", + ) +} + +func TestTakeDeepStack(t *testing.T) { + const ( + N = 500 + withStackDepthName = "go.uber.org/zap/internal/stacktrace.withStackDepth" + ) + withStackDepth(N, func() { + trace := Take(0) + for found := 0; found < N; found++ { + i := strings.Index(trace, withStackDepthName) + if i < 0 { + t.Fatalf(`expected %v occurrences of %q, found %d`, + N, withStackDepthName, found) + } + trace = trace[i+len(withStackDepthName):] + } + }) +} + +func BenchmarkTake(b *testing.B) { + for i := 0; i < b.N; i++ { + Take(0) + } +} + +func withStackDepth(depth int, f func()) { + var recurse func(rune) rune + recurse = func(r rune) rune { + if r > 0 { + bytes.Map(recurse, []byte(string([]rune{r - 1}))) + } else { + f() + } + return 0 + } + recurse(rune(depth)) +} diff --git a/vendor/go.uber.org/zap/internal/stacktrace/ya.make b/vendor/go.uber.org/zap/internal/stacktrace/ya.make new file mode 100644 index 0000000000..de4f0672f0 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/stacktrace/ya.make @@ -0,0 +1,15 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + stack.go +) + +GO_TEST_SRCS(stack_test.go) + +END() + +RECURSE( + gotest +) diff --git a/vendor/go.uber.org/zap/internal/ya.make b/vendor/go.uber.org/zap/internal/ya.make new file mode 100644 index 0000000000..4515b67ad1 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/ya.make @@ -0,0 +1,19 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + level_enabler.go +) + +END() + +RECURSE( + bufferpool + color + exit + pool + readme + stacktrace + ztest +) diff --git a/vendor/go.uber.org/zap/internal/ztest/clock.go b/vendor/go.uber.org/zap/internal/ztest/clock.go new file mode 100644 index 0000000000..47b0b7f965 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/ztest/clock.go @@ -0,0 +1,153 @@ +// Copyright (c) 2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package ztest + +import ( + "sort" + "sync" + "time" +) + +// MockClock is a fake source of time. +// It implements standard time operations, +// but allows the user to control the passage of time. +// +// Use the [Add] method to progress time. +type MockClock struct { + mu sync.RWMutex + now time.Time + + // The MockClock works by maintaining a list of waiters. + // Each waiter knows the time at which it should be resolved. + // When the clock advances, all waiters that are in range are resolved + // in chronological order. + waiters []waiter +} + +// NewMockClock builds a new mock clock +// using the current actual time as the initial time. +func NewMockClock() *MockClock { + return &MockClock{ + now: time.Now(), + } +} + +// Now reports the current time. +func (c *MockClock) Now() time.Time { + c.mu.RLock() + defer c.mu.RUnlock() + return c.now +} + +// NewTicker returns a time.Ticker that ticks at the specified frequency. +// +// As with [time.NewTicker], +// the ticker will drop ticks if the receiver is slow, +// and the channel is never closed. +// +// Calling Stop on the returned ticker is a no-op. +// The ticker only runs when the clock is advanced. +func (c *MockClock) NewTicker(d time.Duration) *time.Ticker { + ch := make(chan time.Time, 1) + + var tick func(time.Time) + tick = func(now time.Time) { + next := now.Add(d) + c.runAt(next, func() { + defer tick(next) + + select { + case ch <- next: + // ok + default: + // The receiver is slow. + // Drop the tick and continue. + } + }) + } + tick(c.Now()) + + return &time.Ticker{C: ch} +} + +// runAt schedules the given function to be run at the given time. +// The function runs without a lock held, so it may schedule more work. +func (c *MockClock) runAt(t time.Time, fn func()) { + c.mu.Lock() + defer c.mu.Unlock() + c.waiters = append(c.waiters, waiter{until: t, fn: fn}) +} + +type waiter struct { + until time.Time + fn func() +} + +// Add progresses time by the given duration. +// Other operations waiting for the time to advance +// will be resolved if they are within range. +// +// Side effects of operations waiting for the time to advance +// will take effect on a best-effort basis. +// Avoid racing with operations that have side effects. +// +// Panics if the duration is negative. +func (c *MockClock) Add(d time.Duration) { + if d < 0 { + panic("cannot add negative duration") + } + + c.mu.Lock() + defer c.mu.Unlock() + + sort.Slice(c.waiters, func(i, j int) bool { + return c.waiters[i].until.Before(c.waiters[j].until) + }) + + newTime := c.now.Add(d) + // newTime won't be recorded until the end of this method. + // This ensures that any waiters that are resolved + // are resolved at the time they were expecting. + + for len(c.waiters) > 0 { + w := c.waiters[0] + if w.until.After(newTime) { + break + } + c.waiters[0] = waiter{} // avoid memory leak + c.waiters = c.waiters[1:] + + // The waiter is within range. + // Travel to the time of the waiter and resolve it. + c.now = w.until + + // The waiter may schedule more work + // so we must release the lock. + c.mu.Unlock() + w.fn() + // Sleeping here is necessary to let the side effects of waiters + // take effect before we continue. + time.Sleep(1 * time.Millisecond) + c.mu.Lock() + } + + c.now = newTime +} diff --git a/vendor/go.uber.org/zap/internal/ztest/clock_test.go b/vendor/go.uber.org/zap/internal/ztest/clock_test.go new file mode 100644 index 0000000000..6db724bd97 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/ztest/clock_test.go @@ -0,0 +1,80 @@ +// Copyright (c) 2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package ztest + +import ( + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestMockClock_NewTicker(t *testing.T) { + var n atomic.Int32 + clock := NewMockClock() + + done := make(chan struct{}) + defer func() { <-done }() // wait for end + + quit := make(chan struct{}) + // Create a channel to increment every microsecond. + go func(ticker *time.Ticker) { + defer close(done) + for { + select { + case <-quit: + ticker.Stop() + return + case <-ticker.C: + n.Add(1) + } + } + }(clock.NewTicker(time.Microsecond)) + + // Move clock forward. + clock.Add(2 * time.Microsecond) + assert.Equal(t, int32(2), n.Load()) + close(quit) +} + +func TestMockClock_NewTicker_slowConsumer(t *testing.T) { + clock := NewMockClock() + + ticker := clock.NewTicker(time.Microsecond) + defer ticker.Stop() + + // Two ticks, only one consumed. + clock.Add(2 * time.Microsecond) + <-ticker.C + + select { + case <-ticker.C: + t.Fatal("unexpected tick") + default: + // ok + } +} + +func TestMockClock_Add_negative(t *testing.T) { + clock := NewMockClock() + assert.Panics(t, func() { clock.Add(-1) }) +} diff --git a/vendor/go.uber.org/zap/internal/ztest/doc.go b/vendor/go.uber.org/zap/internal/ztest/doc.go new file mode 100644 index 0000000000..cd4b98cbcb --- /dev/null +++ b/vendor/go.uber.org/zap/internal/ztest/doc.go @@ -0,0 +1,24 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package ztest provides low-level helpers for testing log output. These +// utilities are helpful in zap's own unit tests, but any assertions using +// them are strongly coupled to a single encoding. +package ztest // import "go.uber.org/zap/internal/ztest" diff --git a/vendor/go.uber.org/zap/internal/ztest/gotest/ya.make b/vendor/go.uber.org/zap/internal/ztest/gotest/ya.make new file mode 100644 index 0000000000..708f864916 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/ztest/gotest/ya.make @@ -0,0 +1,5 @@ +GO_TEST_FOR(vendor/go.uber.org/zap/internal/ztest) + +LICENSE(MIT) + +END() diff --git a/vendor/go.uber.org/zap/internal/ztest/timeout.go b/vendor/go.uber.org/zap/internal/ztest/timeout.go new file mode 100644 index 0000000000..e4222f9479 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/ztest/timeout.go @@ -0,0 +1,59 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package ztest + +import ( + "log" + "os" + "strconv" + "time" +) + +var _timeoutScale = 1.0 + +// Timeout scales the provided duration by $TEST_TIMEOUT_SCALE. +func Timeout(base time.Duration) time.Duration { + return time.Duration(float64(base) * _timeoutScale) +} + +// Sleep scales the sleep duration by $TEST_TIMEOUT_SCALE. +func Sleep(base time.Duration) { + time.Sleep(Timeout(base)) +} + +// Initialize checks the environment and alters the timeout scale accordingly. +// It returns a function to undo the scaling. +func Initialize(factor string) func() { + fv, err := strconv.ParseFloat(factor, 64) + if err != nil { + panic(err) + } + original := _timeoutScale + _timeoutScale = fv + return func() { _timeoutScale = original } +} + +func init() { + if v := os.Getenv("TEST_TIMEOUT_SCALE"); v != "" { + Initialize(v) + log.Printf("Scaling timeouts by %vx.\n", _timeoutScale) + } +} diff --git a/vendor/go.uber.org/zap/internal/ztest/writer.go b/vendor/go.uber.org/zap/internal/ztest/writer.go new file mode 100644 index 0000000000..f54d8569ee --- /dev/null +++ b/vendor/go.uber.org/zap/internal/ztest/writer.go @@ -0,0 +1,96 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package ztest + +import ( + "bytes" + "errors" + "io" + "strings" +) + +// A Syncer is a spy for the Sync portion of zapcore.WriteSyncer. +type Syncer struct { + err error + called bool +} + +// SetError sets the error that the Sync method will return. +func (s *Syncer) SetError(err error) { + s.err = err +} + +// Sync records that it was called, then returns the user-supplied error (if +// any). +func (s *Syncer) Sync() error { + s.called = true + return s.err +} + +// Called reports whether the Sync method was called. +func (s *Syncer) Called() bool { + return s.called +} + +// A Discarder sends all writes to io.Discard. +type Discarder struct{ Syncer } + +// Write implements io.Writer. +func (d *Discarder) Write(b []byte) (int, error) { + return io.Discard.Write(b) +} + +// FailWriter is a WriteSyncer that always returns an error on writes. +type FailWriter struct{ Syncer } + +// Write implements io.Writer. +func (w FailWriter) Write(b []byte) (int, error) { + return len(b), errors.New("failed") +} + +// ShortWriter is a WriteSyncer whose write method never fails, but +// nevertheless fails to the last byte of the input. +type ShortWriter struct{ Syncer } + +// Write implements io.Writer. +func (w ShortWriter) Write(b []byte) (int, error) { + return len(b) - 1, nil +} + +// Buffer is an implementation of zapcore.WriteSyncer that sends all writes to +// a bytes.Buffer. It has convenience methods to split the accumulated buffer +// on newlines. +type Buffer struct { + bytes.Buffer + Syncer +} + +// Lines returns the current buffer contents, split on newlines. +func (b *Buffer) Lines() []string { + output := strings.Split(b.String(), "\n") + return output[:len(output)-1] +} + +// Stripped returns the current buffer contents with the last trailing newline +// stripped. +func (b *Buffer) Stripped() string { + return strings.TrimRight(b.String(), "\n") +} diff --git a/vendor/go.uber.org/zap/internal/ztest/ya.make b/vendor/go.uber.org/zap/internal/ztest/ya.make new file mode 100644 index 0000000000..f66ad3b511 --- /dev/null +++ b/vendor/go.uber.org/zap/internal/ztest/ya.make @@ -0,0 +1,18 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + clock.go + doc.go + timeout.go + writer.go +) + +GO_TEST_SRCS(clock_test.go) + +END() + +RECURSE( + gotest +) diff --git a/vendor/go.uber.org/zap/leak_test.go b/vendor/go.uber.org/zap/leak_test.go new file mode 100644 index 0000000000..474ed2f2e3 --- /dev/null +++ b/vendor/go.uber.org/zap/leak_test.go @@ -0,0 +1,31 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "testing" + + "go.uber.org/goleak" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} diff --git a/vendor/go.uber.org/zap/level.go b/vendor/go.uber.org/zap/level.go new file mode 100644 index 0000000000..155b208bd3 --- /dev/null +++ b/vendor/go.uber.org/zap/level.go @@ -0,0 +1,153 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "sync/atomic" + + "go.uber.org/zap/internal" + "go.uber.org/zap/zapcore" +) + +const ( + // DebugLevel logs are typically voluminous, and are usually disabled in + // production. + DebugLevel = zapcore.DebugLevel + // InfoLevel is the default logging priority. + InfoLevel = zapcore.InfoLevel + // WarnLevel logs are more important than Info, but don't need individual + // human review. + WarnLevel = zapcore.WarnLevel + // ErrorLevel logs are high-priority. If an application is running smoothly, + // it shouldn't generate any error-level logs. + ErrorLevel = zapcore.ErrorLevel + // DPanicLevel logs are particularly important errors. In development the + // logger panics after writing the message. + DPanicLevel = zapcore.DPanicLevel + // PanicLevel logs a message, then panics. + PanicLevel = zapcore.PanicLevel + // FatalLevel logs a message, then calls os.Exit(1). + FatalLevel = zapcore.FatalLevel +) + +// LevelEnablerFunc is a convenient way to implement zapcore.LevelEnabler with +// an anonymous function. +// +// It's particularly useful when splitting log output between different +// outputs (e.g., standard error and standard out). For sample code, see the +// package-level AdvancedConfiguration example. +type LevelEnablerFunc func(zapcore.Level) bool + +// Enabled calls the wrapped function. +func (f LevelEnablerFunc) Enabled(lvl zapcore.Level) bool { return f(lvl) } + +// An AtomicLevel is an atomically changeable, dynamic logging level. It lets +// you safely change the log level of a tree of loggers (the root logger and +// any children created by adding context) at runtime. +// +// The AtomicLevel itself is an http.Handler that serves a JSON endpoint to +// alter its level. +// +// AtomicLevels must be created with the NewAtomicLevel constructor to allocate +// their internal atomic pointer. +type AtomicLevel struct { + l *atomic.Int32 +} + +var _ internal.LeveledEnabler = AtomicLevel{} + +// NewAtomicLevel creates an AtomicLevel with InfoLevel and above logging +// enabled. +func NewAtomicLevel() AtomicLevel { + lvl := AtomicLevel{l: new(atomic.Int32)} + lvl.l.Store(int32(InfoLevel)) + return lvl +} + +// NewAtomicLevelAt is a convenience function that creates an AtomicLevel +// and then calls SetLevel with the given level. +func NewAtomicLevelAt(l zapcore.Level) AtomicLevel { + a := NewAtomicLevel() + a.SetLevel(l) + return a +} + +// ParseAtomicLevel parses an AtomicLevel based on a lowercase or all-caps ASCII +// representation of the log level. If the provided ASCII representation is +// invalid an error is returned. +// +// This is particularly useful when dealing with text input to configure log +// levels. +func ParseAtomicLevel(text string) (AtomicLevel, error) { + a := NewAtomicLevel() + l, err := zapcore.ParseLevel(text) + if err != nil { + return a, err + } + + a.SetLevel(l) + return a, nil +} + +// Enabled implements the zapcore.LevelEnabler interface, which allows the +// AtomicLevel to be used in place of traditional static levels. +func (lvl AtomicLevel) Enabled(l zapcore.Level) bool { + return lvl.Level().Enabled(l) +} + +// Level returns the minimum enabled log level. +func (lvl AtomicLevel) Level() zapcore.Level { + return zapcore.Level(int8(lvl.l.Load())) +} + +// SetLevel alters the logging level. +func (lvl AtomicLevel) SetLevel(l zapcore.Level) { + lvl.l.Store(int32(l)) +} + +// String returns the string representation of the underlying Level. +func (lvl AtomicLevel) String() string { + return lvl.Level().String() +} + +// UnmarshalText unmarshals the text to an AtomicLevel. It uses the same text +// representations as the static zapcore.Levels ("debug", "info", "warn", +// "error", "dpanic", "panic", and "fatal"). +func (lvl *AtomicLevel) UnmarshalText(text []byte) error { + if lvl.l == nil { + lvl.l = &atomic.Int32{} + } + + var l zapcore.Level + if err := l.UnmarshalText(text); err != nil { + return err + } + + lvl.SetLevel(l) + return nil +} + +// MarshalText marshals the AtomicLevel to a byte slice. It uses the same +// text representation as the static zapcore.Levels ("debug", "info", "warn", +// "error", "dpanic", "panic", and "fatal"). +func (lvl AtomicLevel) MarshalText() (text []byte, err error) { + return lvl.Level().MarshalText() +} diff --git a/vendor/go.uber.org/zap/level_test.go b/vendor/go.uber.org/zap/level_test.go new file mode 100644 index 0000000000..db7391d9bb --- /dev/null +++ b/vendor/go.uber.org/zap/level_test.go @@ -0,0 +1,140 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "sync" + "testing" + + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLevelEnablerFunc(t *testing.T) { + enab := LevelEnablerFunc(func(l zapcore.Level) bool { return l == zapcore.InfoLevel }) + tests := []struct { + level zapcore.Level + enabled bool + }{ + {DebugLevel, false}, + {InfoLevel, true}, + {WarnLevel, false}, + {ErrorLevel, false}, + {DPanicLevel, false}, + {PanicLevel, false}, + {FatalLevel, false}, + } + for _, tt := range tests { + assert.Equal(t, tt.enabled, enab.Enabled(tt.level), "Unexpected result applying LevelEnablerFunc to %s", tt.level) + } +} + +func TestNewAtomicLevel(t *testing.T) { + lvl := NewAtomicLevel() + assert.Equal(t, InfoLevel, lvl.Level(), "Unexpected initial level.") + lvl.SetLevel(ErrorLevel) + assert.Equal(t, ErrorLevel, lvl.Level(), "Unexpected level after SetLevel.") + lvl = NewAtomicLevelAt(WarnLevel) + assert.Equal(t, WarnLevel, lvl.Level(), "Unexpected level after SetLevel.") +} + +func TestParseAtomicLevel(t *testing.T) { + tests := []struct { + text string + level AtomicLevel + err string + }{ + {"info", NewAtomicLevel(), ""}, + {"DEBUG", NewAtomicLevelAt(DebugLevel), ""}, + {"FOO", NewAtomicLevel(), `unrecognized level: "FOO"`}, + } + + for _, tt := range tests { + parsedAtomicLevel, err := ParseAtomicLevel(tt.text) + if len(tt.err) == 0 { + require.NoError(t, err) + assert.Equal(t, tt.level, parsedAtomicLevel) + } else { + assert.ErrorContains(t, err, tt.err) + } + } +} + +func TestAtomicLevelMutation(t *testing.T) { + lvl := NewAtomicLevel() + lvl.SetLevel(WarnLevel) + // Trigger races for non-atomic level mutations. + proceed := make(chan struct{}) + wg := &sync.WaitGroup{} + runConcurrently(10, 100, wg, func() { + <-proceed + assert.Equal(t, WarnLevel, lvl.Level()) + }) + runConcurrently(10, 100, wg, func() { + <-proceed + lvl.SetLevel(WarnLevel) + }) + close(proceed) + wg.Wait() +} + +func TestAtomicLevelText(t *testing.T) { + tests := []struct { + text string + expect zapcore.Level + err bool + }{ + {"debug", DebugLevel, false}, + {"info", InfoLevel, false}, + {"", InfoLevel, false}, + {"warn", WarnLevel, false}, + {"error", ErrorLevel, false}, + {"dpanic", DPanicLevel, false}, + {"panic", PanicLevel, false}, + {"fatal", FatalLevel, false}, + {"foobar", InfoLevel, true}, + } + + for _, tt := range tests { + var lvl AtomicLevel + // Test both initial unmarshaling and overwriting existing value. + for i := 0; i < 2; i++ { + if tt.err { + assert.Error(t, lvl.UnmarshalText([]byte(tt.text)), "Expected unmarshaling %q to fail.", tt.text) + } else { + assert.NoError(t, lvl.UnmarshalText([]byte(tt.text)), "Expected unmarshaling %q to succeed.", tt.text) + } + assert.Equal(t, tt.expect, lvl.Level(), "Unexpected level after unmarshaling.") + lvl.SetLevel(InfoLevel) + } + + // Test marshalling + if tt.text != "" && !tt.err { + lvl.SetLevel(tt.expect) + marshaled, err := lvl.MarshalText() + assert.NoError(t, err, `Unexpected error marshalling level "%v" to text.`, tt.expect) + assert.Equal(t, tt.text, string(marshaled), "Expected marshaled text to match") + assert.Equal(t, tt.text, lvl.String(), "Expected Stringer call to match") + } + } +} diff --git a/vendor/go.uber.org/zap/logger.go b/vendor/go.uber.org/zap/logger.go new file mode 100644 index 0000000000..6205fe48a6 --- /dev/null +++ b/vendor/go.uber.org/zap/logger.go @@ -0,0 +1,432 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "fmt" + "io" + "os" + "strings" + + "go.uber.org/zap/internal/bufferpool" + "go.uber.org/zap/internal/stacktrace" + "go.uber.org/zap/zapcore" +) + +// A Logger provides fast, leveled, structured logging. All methods are safe +// for concurrent use. +// +// The Logger is designed for contexts in which every microsecond and every +// allocation matters, so its API intentionally favors performance and type +// safety over brevity. For most applications, the SugaredLogger strikes a +// better balance between performance and ergonomics. +type Logger struct { + core zapcore.Core + + development bool + addCaller bool + onFatal zapcore.CheckWriteHook // default is WriteThenFatal + + name string + errorOutput zapcore.WriteSyncer + + addStack zapcore.LevelEnabler + + callerSkip int + + clock zapcore.Clock +} + +// New constructs a new Logger from the provided zapcore.Core and Options. If +// the passed zapcore.Core is nil, it falls back to using a no-op +// implementation. +// +// This is the most flexible way to construct a Logger, but also the most +// verbose. For typical use cases, the highly-opinionated presets +// (NewProduction, NewDevelopment, and NewExample) or the Config struct are +// more convenient. +// +// For sample code, see the package-level AdvancedConfiguration example. +func New(core zapcore.Core, options ...Option) *Logger { + if core == nil { + return NewNop() + } + log := &Logger{ + core: core, + errorOutput: zapcore.Lock(os.Stderr), + addStack: zapcore.FatalLevel + 1, + clock: zapcore.DefaultClock, + } + return log.WithOptions(options...) +} + +// NewNop returns a no-op Logger. It never writes out logs or internal errors, +// and it never runs user-defined hooks. +// +// Using WithOptions to replace the Core or error output of a no-op Logger can +// re-enable logging. +func NewNop() *Logger { + return &Logger{ + core: zapcore.NewNopCore(), + errorOutput: zapcore.AddSync(io.Discard), + addStack: zapcore.FatalLevel + 1, + clock: zapcore.DefaultClock, + } +} + +// NewProduction builds a sensible production Logger that writes InfoLevel and +// above logs to standard error as JSON. +// +// It's a shortcut for NewProductionConfig().Build(...Option). +func NewProduction(options ...Option) (*Logger, error) { + return NewProductionConfig().Build(options...) +} + +// NewDevelopment builds a development Logger that writes DebugLevel and above +// logs to standard error in a human-friendly format. +// +// It's a shortcut for NewDevelopmentConfig().Build(...Option). +func NewDevelopment(options ...Option) (*Logger, error) { + return NewDevelopmentConfig().Build(options...) +} + +// Must is a helper that wraps a call to a function returning (*Logger, error) +// and panics if the error is non-nil. It is intended for use in variable +// initialization such as: +// +// var logger = zap.Must(zap.NewProduction()) +func Must(logger *Logger, err error) *Logger { + if err != nil { + panic(err) + } + + return logger +} + +// NewExample builds a Logger that's designed for use in zap's testable +// examples. It writes DebugLevel and above logs to standard out as JSON, but +// omits the timestamp and calling function to keep example output +// short and deterministic. +func NewExample(options ...Option) *Logger { + encoderCfg := zapcore.EncoderConfig{ + MessageKey: "msg", + LevelKey: "level", + NameKey: "logger", + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + } + core := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), os.Stdout, DebugLevel) + return New(core).WithOptions(options...) +} + +// Sugar wraps the Logger to provide a more ergonomic, but slightly slower, +// API. Sugaring a Logger is quite inexpensive, so it's reasonable for a +// single application to use both Loggers and SugaredLoggers, converting +// between them on the boundaries of performance-sensitive code. +func (log *Logger) Sugar() *SugaredLogger { + core := log.clone() + core.callerSkip += 2 + return &SugaredLogger{core} +} + +// Named adds a new path segment to the logger's name. Segments are joined by +// periods. By default, Loggers are unnamed. +func (log *Logger) Named(s string) *Logger { + if s == "" { + return log + } + l := log.clone() + if log.name == "" { + l.name = s + } else { + l.name = strings.Join([]string{l.name, s}, ".") + } + return l +} + +// WithOptions clones the current Logger, applies the supplied Options, and +// returns the resulting Logger. It's safe to use concurrently. +func (log *Logger) WithOptions(opts ...Option) *Logger { + c := log.clone() + for _, opt := range opts { + opt.apply(c) + } + return c +} + +// With creates a child logger and adds structured context to it. Fields added +// to the child don't affect the parent, and vice versa. Any fields that +// require evaluation (such as Objects) are evaluated upon invocation of With. +func (log *Logger) With(fields ...Field) *Logger { + if len(fields) == 0 { + return log + } + l := log.clone() + l.core = l.core.With(fields) + return l +} + +// WithLazy creates a child logger and adds structured context to it lazily. +// +// The fields are evaluated only if the logger is further chained with [With] +// or is written to with any of the log level methods. +// Until that occurs, the logger may retain references to objects inside the fields, +// and logging will reflect the state of an object at the time of logging, +// not the time of WithLazy(). +// +// WithLazy provides a worthwhile performance optimization for contextual loggers +// when the likelihood of using the child logger is low, +// such as error paths and rarely taken branches. +// +// Similar to [With], fields added to the child don't affect the parent, and vice versa. +func (log *Logger) WithLazy(fields ...Field) *Logger { + if len(fields) == 0 { + return log + } + return log.WithOptions(WrapCore(func(core zapcore.Core) zapcore.Core { + return zapcore.NewLazyWith(core, fields) + })) +} + +// Level reports the minimum enabled level for this logger. +// +// For NopLoggers, this is [zapcore.InvalidLevel]. +func (log *Logger) Level() zapcore.Level { + return zapcore.LevelOf(log.core) +} + +// Check returns a CheckedEntry if logging a message at the specified level +// is enabled. It's a completely optional optimization; in high-performance +// applications, Check can help avoid allocating a slice to hold fields. +func (log *Logger) Check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { + return log.check(lvl, msg) +} + +// Log logs a message at the specified level. The message includes any fields +// passed at the log site, as well as any fields accumulated on the logger. +// Any Fields that require evaluation (such as Objects) are evaluated upon +// invocation of Log. +func (log *Logger) Log(lvl zapcore.Level, msg string, fields ...Field) { + if ce := log.check(lvl, msg); ce != nil { + ce.Write(fields...) + } +} + +// Debug logs a message at DebugLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +func (log *Logger) Debug(msg string, fields ...Field) { + if ce := log.check(DebugLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Info logs a message at InfoLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +func (log *Logger) Info(msg string, fields ...Field) { + if ce := log.check(InfoLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Warn logs a message at WarnLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +func (log *Logger) Warn(msg string, fields ...Field) { + if ce := log.check(WarnLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Error logs a message at ErrorLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +func (log *Logger) Error(msg string, fields ...Field) { + if ce := log.check(ErrorLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// DPanic logs a message at DPanicLevel. The message includes any fields +// passed at the log site, as well as any fields accumulated on the logger. +// +// If the logger is in development mode, it then panics (DPanic means +// "development panic"). This is useful for catching errors that are +// recoverable, but shouldn't ever happen. +func (log *Logger) DPanic(msg string, fields ...Field) { + if ce := log.check(DPanicLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Panic logs a message at PanicLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +// +// The logger then panics, even if logging at PanicLevel is disabled. +func (log *Logger) Panic(msg string, fields ...Field) { + if ce := log.check(PanicLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Fatal logs a message at FatalLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +// +// The logger then calls os.Exit(1), even if logging at FatalLevel is +// disabled. +func (log *Logger) Fatal(msg string, fields ...Field) { + if ce := log.check(FatalLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Sync calls the underlying Core's Sync method, flushing any buffered log +// entries. Applications should take care to call Sync before exiting. +func (log *Logger) Sync() error { + return log.core.Sync() +} + +// Core returns the Logger's underlying zapcore.Core. +func (log *Logger) Core() zapcore.Core { + return log.core +} + +// Name returns the Logger's underlying name, +// or an empty string if the logger is unnamed. +func (log *Logger) Name() string { + return log.name +} + +func (log *Logger) clone() *Logger { + clone := *log + return &clone +} + +func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { + // Logger.check must always be called directly by a method in the + // Logger interface (e.g., Check, Info, Fatal). + // This skips Logger.check and the Info/Fatal/Check/etc. method that + // called it. + const callerSkipOffset = 2 + + // Check the level first to reduce the cost of disabled log calls. + // Since Panic and higher may exit, we skip the optimization for those levels. + if lvl < zapcore.DPanicLevel && !log.core.Enabled(lvl) { + return nil + } + + // Create basic checked entry thru the core; this will be non-nil if the + // log message will actually be written somewhere. + ent := zapcore.Entry{ + LoggerName: log.name, + Time: log.clock.Now(), + Level: lvl, + Message: msg, + } + ce := log.core.Check(ent, nil) + willWrite := ce != nil + + // Set up any required terminal behavior. + switch ent.Level { + case zapcore.PanicLevel: + ce = ce.After(ent, zapcore.WriteThenPanic) + case zapcore.FatalLevel: + onFatal := log.onFatal + // nil or WriteThenNoop will lead to continued execution after + // a Fatal log entry, which is unexpected. For example, + // + // f, err := os.Open(..) + // if err != nil { + // log.Fatal("cannot open", zap.Error(err)) + // } + // fmt.Println(f.Name()) + // + // The f.Name() will panic if we continue execution after the + // log.Fatal. + if onFatal == nil || onFatal == zapcore.WriteThenNoop { + onFatal = zapcore.WriteThenFatal + } + ce = ce.After(ent, onFatal) + case zapcore.DPanicLevel: + if log.development { + ce = ce.After(ent, zapcore.WriteThenPanic) + } + } + + // Only do further annotation if we're going to write this message; checked + // entries that exist only for terminal behavior don't benefit from + // annotation. + if !willWrite { + return ce + } + + // Thread the error output through to the CheckedEntry. + ce.ErrorOutput = log.errorOutput + + addStack := log.addStack.Enabled(ce.Level) + if !log.addCaller && !addStack { + return ce + } + + // Adding the caller or stack trace requires capturing the callers of + // this function. We'll share information between these two. + stackDepth := stacktrace.First + if addStack { + stackDepth = stacktrace.Full + } + stack := stacktrace.Capture(log.callerSkip+callerSkipOffset, stackDepth) + defer stack.Free() + + if stack.Count() == 0 { + if log.addCaller { + fmt.Fprintf(log.errorOutput, "%v Logger.check error: failed to get caller\n", ent.Time.UTC()) + _ = log.errorOutput.Sync() + } + return ce + } + + frame, more := stack.Next() + + if log.addCaller { + ce.Caller = zapcore.EntryCaller{ + Defined: frame.PC != 0, + PC: frame.PC, + File: frame.File, + Line: frame.Line, + Function: frame.Function, + } + } + + if addStack { + buffer := bufferpool.Get() + defer buffer.Free() + + stackfmt := stacktrace.NewFormatter(buffer) + + // We've already extracted the first frame, so format that + // separately and defer to stackfmt for the rest. + stackfmt.FormatFrame(frame) + if more { + stackfmt.FormatStack(stack) + } + ce.Stack = buffer.String() + } + + return ce +} diff --git a/vendor/go.uber.org/zap/logger_bench_test.go b/vendor/go.uber.org/zap/logger_bench_test.go new file mode 100644 index 0000000000..9d4129826b --- /dev/null +++ b/vendor/go.uber.org/zap/logger_bench_test.go @@ -0,0 +1,361 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "runtime" + "strconv" + "sync" + "testing" + "time" + + "go.uber.org/zap/internal/ztest" + "go.uber.org/zap/zapcore" +) + +type user struct { + Name string + Email string + CreatedAt time.Time +} + +func (u *user) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("name", u.Name) + enc.AddString("email", u.Email) + enc.AddInt64("created_at", u.CreatedAt.UnixNano()) + return nil +} + +var _jane = &user{ + Name: "Jane Doe", + Email: "jane@test.com", + CreatedAt: time.Date(1980, 1, 1, 12, 0, 0, 0, time.UTC), +} + +func withBenchedLogger(b *testing.B, f func(*Logger)) { + logger := New( + zapcore.NewCore( + zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig), + &ztest.Discarder{}, + DebugLevel, + )) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + f(logger) + } + }) +} + +func BenchmarkNoContext(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("No context.") + }) +} + +func BenchmarkBoolField(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Boolean.", Bool("foo", true)) + }) +} + +func BenchmarkByteStringField(b *testing.B) { + val := []byte("bar") + withBenchedLogger(b, func(log *Logger) { + log.Info("ByteString.", ByteString("foo", val)) + }) +} + +func BenchmarkFloat64Field(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Floating point.", Float64("foo", 3.14)) + }) +} + +func BenchmarkIntField(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Integer.", Int("foo", 42)) + }) +} + +func BenchmarkInt64Field(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("64-bit integer.", Int64("foo", 42)) + }) +} + +func BenchmarkStringField(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Strings.", String("foo", "bar")) + }) +} + +func BenchmarkStringerField(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Level.", Stringer("foo", InfoLevel)) + }) +} + +func BenchmarkTimeField(b *testing.B) { + t := time.Unix(0, 0) + withBenchedLogger(b, func(log *Logger) { + log.Info("Time.", Time("foo", t)) + }) +} + +func BenchmarkDurationField(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Duration", Duration("foo", time.Second)) + }) +} + +func BenchmarkErrorField(b *testing.B) { + err := errors.New("egad") + withBenchedLogger(b, func(log *Logger) { + log.Info("Error.", Error(err)) + }) +} + +func BenchmarkErrorsField(b *testing.B) { + errs := []error{ + errors.New("egad"), + errors.New("oh no"), + errors.New("dear me"), + errors.New("such fail"), + } + withBenchedLogger(b, func(log *Logger) { + log.Info("Errors.", Errors("errors", errs)) + }) +} + +func BenchmarkStackField(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Error.", Stack("stacktrace")) + }) +} + +func BenchmarkObjectField(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Arbitrary ObjectMarshaler.", Object("user", _jane)) + }) +} + +func BenchmarkReflectField(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Reflection-based serialization.", Reflect("user", _jane)) + }) +} + +func BenchmarkAddCallerHook(b *testing.B) { + logger := New( + zapcore.NewCore( + zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig), + &ztest.Discarder{}, + InfoLevel, + ), + AddCaller(), + ) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + logger.Info("Caller.") + } + }) +} + +func BenchmarkAddCallerAndStacktrace(b *testing.B) { + logger := New( + zapcore.NewCore( + zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig), + &ztest.Discarder{}, + InfoLevel, + ), + AddCaller(), + AddStacktrace(WarnLevel), + ) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + logger.Warn("Caller and stacktrace.") + } + }) +} + +func Benchmark5WithsUsed(b *testing.B) { + benchmarkWithUsed(b, (*Logger).With, 5, true) +} + +// This benchmark will be used in future as a +// baseline for improving +func Benchmark5WithsNotUsed(b *testing.B) { + benchmarkWithUsed(b, (*Logger).With, 5, false) +} + +func Benchmark5WithLazysUsed(b *testing.B) { + benchmarkWithUsed(b, (*Logger).WithLazy, 5, true) +} + +// This benchmark will be used in future as a +// baseline for improving +func Benchmark5WithLazysNotUsed(b *testing.B) { + benchmarkWithUsed(b, (*Logger).WithLazy, 5, false) +} + +func benchmarkWithUsed(b *testing.B, withMethod func(*Logger, ...zapcore.Field) *Logger, N int, use bool) { + keys := make([]string, N) + values := make([]string, N) + for i := 0; i < N; i++ { + keys[i] = "k" + strconv.Itoa(i) + values[i] = "v" + strconv.Itoa(i) + } + + b.ResetTimer() + + withBenchedLogger(b, func(log *Logger) { + for i := 0; i < N; i++ { + log = withMethod(log, String(keys[i], values[i])) + } + if use { + log.Info("used") + return + } + runtime.KeepAlive(log) + }) +} + +func Benchmark10Fields(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Ten fields, passed at the log site.", + Int("one", 1), + Int("two", 2), + Int("three", 3), + Int("four", 4), + Int("five", 5), + Int("six", 6), + Int("seven", 7), + Int("eight", 8), + Int("nine", 9), + Int("ten", 10), + ) + }) +} + +func Benchmark100Fields(b *testing.B) { + const batchSize = 50 + logger := New(zapcore.NewCore( + zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig), + &ztest.Discarder{}, + DebugLevel, + )) + + // Don't include allocating these helper slices in the benchmark. Since + // access to them isn't synchronized, we can't run the benchmark in + // parallel. + first := make([]Field, batchSize) + second := make([]Field, batchSize) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + for i := 0; i < batchSize; i++ { + // We're duplicating keys, but that doesn't affect performance. + first[i] = Int("foo", i) + second[i] = Int("foo", i+batchSize) + } + logger.With(first...).Info("Child loggers with lots of context.", second...) + } +} + +func BenchmarkAny(b *testing.B) { + key := "some-long-string-longer-than-16" + + tests := []struct { + name string + typed func() Field + anyArg any + }{ + { + name: "string", + typed: func() Field { return String(key, "yet-another-long-string") }, + anyArg: "yet-another-long-string", + }, + { + name: "stringer", + typed: func() Field { return Stringer(key, InfoLevel) }, + anyArg: InfoLevel, + }, + } + + for _, tt := range tests { + b.Run(tt.name, func(b *testing.B) { + b.Run("field-only", func(b *testing.B) { + b.Run("typed", func(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + f := tt.typed() + runtime.KeepAlive(f) + }) + }) + b.Run("any", func(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + f := Any(key, tt.anyArg) + runtime.KeepAlive(f) + }) + }) + }) + b.Run("log", func(b *testing.B) { + b.Run("typed", func(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("", tt.typed()) + }) + }) + b.Run("any", func(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("", Any(key, tt.anyArg)) + }) + }) + }) + b.Run("log-go", func(b *testing.B) { + b.Run("typed", func(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + var wg sync.WaitGroup + wg.Add(1) + go func() { + log.Info("", tt.typed()) + wg.Done() + }() + wg.Wait() + }) + }) + b.Run("any", func(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + var wg sync.WaitGroup + wg.Add(1) + go func() { + log.Info("", Any(key, tt.anyArg)) + wg.Done() + }() + wg.Wait() + }) + }) + }) + }) + } +} diff --git a/vendor/go.uber.org/zap/logger_test.go b/vendor/go.uber.org/zap/logger_test.go new file mode 100644 index 0000000000..c371b81228 --- /dev/null +++ b/vendor/go.uber.org/zap/logger_test.go @@ -0,0 +1,938 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "fmt" + "strconv" + "sync" + "sync/atomic" + "testing" + + "go.uber.org/zap/internal/exit" + "go.uber.org/zap/internal/ztest" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func makeCountingHook() (func(zapcore.Entry) error, *atomic.Int64) { + count := &atomic.Int64{} + h := func(zapcore.Entry) error { + count.Add(1) + return nil + } + return h, count +} + +func TestLoggerAtomicLevel(t *testing.T) { + // Test that the dynamic level applies to all ancestors and descendants. + dl := NewAtomicLevel() + + withLogger(t, dl, nil, func(grandparent *Logger, _ *observer.ObservedLogs) { + parent := grandparent.With(Int("generation", 1)) + child := parent.With(Int("generation", 2)) + + tests := []struct { + setLevel zapcore.Level + testLevel zapcore.Level + enabled bool + }{ + {DebugLevel, DebugLevel, true}, + {InfoLevel, DebugLevel, false}, + {WarnLevel, PanicLevel, true}, + } + + for _, tt := range tests { + dl.SetLevel(tt.setLevel) + for _, logger := range []*Logger{grandparent, parent, child} { + if tt.enabled { + assert.NotNil( + t, + logger.Check(tt.testLevel, ""), + "Expected level %s to be enabled after setting level %s.", tt.testLevel, tt.setLevel, + ) + } else { + assert.Nil( + t, + logger.Check(tt.testLevel, ""), + "Expected level %s to be enabled after setting level %s.", tt.testLevel, tt.setLevel, + ) + } + } + } + }) +} + +func TestLoggerLevel(t *testing.T) { + levels := []zapcore.Level{ + DebugLevel, + InfoLevel, + WarnLevel, + ErrorLevel, + DPanicLevel, + PanicLevel, + FatalLevel, + } + + for _, lvl := range levels { + lvl := lvl + t.Run(lvl.String(), func(t *testing.T) { + t.Parallel() + + core, _ := observer.New(lvl) + log := New(core) + assert.Equal(t, lvl, log.Level()) + }) + } + + t.Run("Nop", func(t *testing.T) { + assert.Equal(t, zapcore.InvalidLevel, NewNop().Level()) + }) +} + +func TestLoggerInitialFields(t *testing.T) { + fieldOpts := opts(Fields(Int("foo", 42), String("bar", "baz"))) + withLogger(t, DebugLevel, fieldOpts, func(logger *Logger, logs *observer.ObservedLogs) { + logger.Info("") + assert.Equal( + t, + observer.LoggedEntry{Context: []Field{Int("foo", 42), String("bar", "baz")}}, + logs.AllUntimed()[0], + "Unexpected output with initial fields set.", + ) + }) +} + +func TestLoggerWith(t *testing.T) { + + tests := []struct { + name string + initialFields []Field + withMethod func(*Logger, ...Field) *Logger + }{ + { + "regular non lazy logger", + []Field{Int("foo", 42)}, + (*Logger).With, + }, + { + "regular non lazy logger no initial fields", + []Field{}, + (*Logger).With, + }, + { + "lazy with logger", + []Field{Int("foo", 42)}, + (*Logger).WithLazy, + }, + { + "lazy with logger no initial fields", + []Field{}, + (*Logger).WithLazy, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + withLogger(t, DebugLevel, opts(Fields(tt.initialFields...)), func(logger *Logger, logs *observer.ObservedLogs) { + // Child loggers should have copy-on-write semantics, so two children + // shouldn't stomp on each other's fields or affect the parent's fields. + tt.withMethod(logger).Info("") + tt.withMethod(logger, String("one", "two")).Info("") + tt.withMethod(logger, String("three", "four")).Info("") + tt.withMethod(logger, String("five", "six")).With(String("seven", "eight")).Info("") + logger.Info("") + + assert.Equal(t, []observer.LoggedEntry{ + {Context: tt.initialFields}, + {Context: append(tt.initialFields, String("one", "two"))}, + {Context: append(tt.initialFields, String("three", "four"))}, + {Context: append(tt.initialFields, String("five", "six"), String("seven", "eight"))}, + {Context: tt.initialFields}, + }, logs.AllUntimed(), "Unexpected cross-talk between child loggers.") + }) + }) + } +} + +func TestLoggerWithCaptures(t *testing.T) { + type withF func(*Logger, ...Field) *Logger + tests := []struct { + name string + withMethods []withF + wantJSON []string + }{ + { + name: "regular with captures arguments at time of With", + withMethods: []withF{(*Logger).With}, + wantJSON: []string{ + `{ + "m": "hello 0", + "a0": [0], + "b0": [1] + }`, + `{ + "m": "world 0", + "a0": [0], + "c0": [2] + }`, + }, + }, + { + name: "lazy with captures arguments at time of With or Logging", + withMethods: []withF{(*Logger).WithLazy}, + wantJSON: []string{ + `{ + "m": "hello 0", + "a0": [1], + "b0": [1] + }`, + `{ + "m": "world 0", + "a0": [1], + "c0": [2] + }`, + }, + }, + { + name: "2x With captures arguments at time of each With", + withMethods: []withF{(*Logger).With, (*Logger).With}, + wantJSON: []string{ + `{ + "m": "hello 0", + "a0": [0], + "b0": [1] + }`, + `{ + "m": "world 0", + "a0": [0], + "c0": [2] + }`, + `{ + "m": "hello 1", + "a0": [0], + "c0": [2], + "a1": [10], + "b1": [11] + }`, + `{ + "m": "world 1", + "a0": [0], + "c0": [2], + "a1": [10], + "c1": [12] + }`, + }, + }, + { + name: "2x WithLazy. Captures arguments only at logging time.", + withMethods: []withF{(*Logger).WithLazy, (*Logger).WithLazy}, + wantJSON: []string{ + `{ + "m": "hello 0", + "a0": [1], + "b0": [1] + }`, + `{ + "m": "world 0", + "a0": [1], + "c0": [2] + }`, + `{ + "m": "hello 1", + "a0": [1], + "c0": [2], + "a1": [11], + "b1": [11] + }`, + `{ + "m": "world 1", + "a0": [1], + "c0": [2], + "a1": [11], + "c1": [12] + }`, + }, + }, + { + name: "WithLazy then With", + withMethods: []withF{(*Logger).WithLazy, (*Logger).With}, + wantJSON: []string{ + `{ + "m": "hello 0", + "a0": [1], + "b0": [1] + }`, + `{ + "m": "world 0", + "a0": [1], + "c0": [2] + }`, + `{ + "m": "hello 1", + "a0": [1], + "c0": [2], + "a1": [10], + "b1": [11] + }`, + `{ + "m": "world 1", + "a0": [1], + "c0": [2], + "a1": [10], + "c1": [12] + }`, + }, + }, + { + name: "With then WithLazy", + withMethods: []withF{(*Logger).With, (*Logger).WithLazy}, + wantJSON: []string{ + `{ + "m": "hello 0", + "a0": [0], + "b0": [1] + }`, + `{ + "m": "world 0", + "a0": [0], + "c0": [2] + }`, + `{ + "m": "hello 1", + "a0": [0], + "c0": [2], + "a1": [11], + "b1": [11] + }`, + `{ + "m": "world 1", + "a0": [0], + "c0": [2], + "a1": [11], + "c1": [12] + }`, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{ + MessageKey: "m", + }) + + var bs ztest.Buffer + logger := New(zapcore.NewCore(enc, &bs, DebugLevel)) + + for i, withMethod := range tt.withMethods { + + iStr := strconv.Itoa(i) + x := 10 * i + arr := zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { + enc.AppendInt(x) + return nil + }) + + // Demonstrate the arguments are captured when With() and Info() are invoked. + logger = withMethod(logger, Array("a"+iStr, arr)) + x++ + logger.Info(fmt.Sprintf("hello %d", i), Array("b"+iStr, arr)) + x++ + logger = withMethod(logger, Array("c"+iStr, arr)) + logger.Info(fmt.Sprintf("world %d", i)) + } + + if lines := bs.Lines(); assert.Len(t, lines, len(tt.wantJSON)) { + for i, want := range tt.wantJSON { + assert.JSONEq(t, want, lines[i], "Unexpected output from the %d'th log.", i) + } + } + }) + } +} + +func TestLoggerLogPanic(t *testing.T) { + for _, tt := range []struct { + do func(*Logger) + should bool + expected string + }{ + {func(logger *Logger) { logger.Check(PanicLevel, "foo").Write() }, true, "foo"}, + {func(logger *Logger) { logger.Log(PanicLevel, "bar") }, true, "bar"}, + {func(logger *Logger) { logger.Panic("baz") }, true, "baz"}, + } { + withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) { + if tt.should { + assert.Panics(t, func() { tt.do(logger) }, "Expected panic") + } else { + assert.NotPanics(t, func() { tt.do(logger) }, "Expected no panic") + } + + output := logs.AllUntimed() + assert.Equal(t, 1, len(output), "Unexpected number of logs.") + assert.Equal(t, 0, len(output[0].Context), "Unexpected context on first log.") + assert.Equal( + t, + zapcore.Entry{Message: tt.expected, Level: PanicLevel}, + output[0].Entry, + "Unexpected output from panic-level Log.", + ) + }) + } +} + +func TestLoggerLogFatal(t *testing.T) { + for _, tt := range []struct { + do func(*Logger) + expected string + }{ + {func(logger *Logger) { logger.Check(FatalLevel, "foo").Write() }, "foo"}, + {func(logger *Logger) { logger.Log(FatalLevel, "bar") }, "bar"}, + {func(logger *Logger) { logger.Fatal("baz") }, "baz"}, + } { + withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) { + stub := exit.WithStub(func() { + tt.do(logger) + }) + assert.True(t, stub.Exited, "Expected Fatal logger call to terminate process.") + output := logs.AllUntimed() + assert.Equal(t, 1, len(output), "Unexpected number of logs.") + assert.Equal(t, 0, len(output[0].Context), "Unexpected context on first log.") + assert.Equal( + t, + zapcore.Entry{Message: tt.expected, Level: FatalLevel}, + output[0].Entry, + "Unexpected output from fatal-level Log.", + ) + }) + } +} + +func TestLoggerLeveledMethods(t *testing.T) { + withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) { + tests := []struct { + method func(string, ...Field) + expectedLevel zapcore.Level + }{ + {logger.Debug, DebugLevel}, + {logger.Info, InfoLevel}, + {logger.Warn, WarnLevel}, + {logger.Error, ErrorLevel}, + {logger.DPanic, DPanicLevel}, + } + for i, tt := range tests { + tt.method("") + output := logs.AllUntimed() + assert.Equal(t, i+1, len(output), "Unexpected number of logs.") + assert.Equal(t, 0, len(output[i].Context), "Unexpected context on first log.") + assert.Equal( + t, + zapcore.Entry{Level: tt.expectedLevel}, + output[i].Entry, + "Unexpected output from %s-level logger method.", tt.expectedLevel) + } + }) +} + +func TestLoggerLogLevels(t *testing.T) { + withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) { + levels := []zapcore.Level{ + DebugLevel, + InfoLevel, + WarnLevel, + ErrorLevel, + DPanicLevel, + } + for i, level := range levels { + logger.Log(level, "") + output := logs.AllUntimed() + assert.Equal(t, i+1, len(output), "Unexpected number of logs.") + assert.Equal(t, 0, len(output[i].Context), "Unexpected context on first log.") + assert.Equal( + t, + zapcore.Entry{Level: level}, + output[i].Entry, + "Unexpected output from %s-level logger method.", level) + } + }) +} + +func TestLoggerAlwaysPanics(t *testing.T) { + // Users can disable writing out panic-level logs, but calls to logger.Panic() + // should still call panic(). + withLogger(t, FatalLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) { + msg := "Even if output is disabled, logger.Panic should always panic." + assert.Panics(t, func() { logger.Panic("foo") }, msg) + assert.Panics(t, func() { logger.Log(PanicLevel, "foo") }, msg) + assert.Panics(t, func() { + if ce := logger.Check(PanicLevel, "foo"); ce != nil { + ce.Write() + } + }, msg) + assert.Equal(t, 0, logs.Len(), "Panics shouldn't be written out if PanicLevel is disabled.") + }) +} + +func TestLoggerAlwaysFatals(t *testing.T) { + // Users can disable writing out fatal-level logs, but calls to logger.Fatal() + // should still terminate the process. + withLogger(t, FatalLevel+1, nil, func(logger *Logger, logs *observer.ObservedLogs) { + stub := exit.WithStub(func() { logger.Fatal("") }) + assert.True(t, stub.Exited, "Expected calls to logger.Fatal to terminate process.") + + stub = exit.WithStub(func() { logger.Log(FatalLevel, "") }) + assert.True(t, stub.Exited, "Expected calls to logger.Fatal to terminate process.") + + stub = exit.WithStub(func() { + if ce := logger.Check(FatalLevel, ""); ce != nil { + ce.Write() + } + }) + assert.True(t, stub.Exited, "Expected calls to logger.Check(FatalLevel, ...) to terminate process.") + + assert.Equal(t, 0, logs.Len(), "Shouldn't write out logs when fatal-level logging is disabled.") + }) +} + +func TestLoggerDPanic(t *testing.T) { + withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) { + assert.NotPanics(t, func() { logger.DPanic("") }) + assert.Equal( + t, + []observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []Field{}}}, + logs.AllUntimed(), + "Unexpected log output from DPanic in production mode.", + ) + }) + withLogger(t, DebugLevel, opts(Development()), func(logger *Logger, logs *observer.ObservedLogs) { + assert.Panics(t, func() { logger.DPanic("") }) + assert.Equal( + t, + []observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []Field{}}}, + logs.AllUntimed(), + "Unexpected log output from DPanic in development mode.", + ) + }) +} + +func TestLoggerNoOpsDisabledLevels(t *testing.T) { + withLogger(t, WarnLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) { + logger.Info("silence!") + assert.Equal( + t, + []observer.LoggedEntry{}, + logs.AllUntimed(), + "Expected logging at a disabled level to produce no output.", + ) + }) +} + +func TestLoggerNames(t *testing.T) { + tests := []struct { + names []string + expected string + }{ + {nil, ""}, + {[]string{""}, ""}, + {[]string{"foo"}, "foo"}, + {[]string{"foo", ""}, "foo"}, + {[]string{"foo", "bar"}, "foo.bar"}, + {[]string{"foo.bar", "baz"}, "foo.bar.baz"}, + // Garbage in, garbage out. + {[]string{"foo.", "bar"}, "foo..bar"}, + {[]string{"foo", ".bar"}, "foo..bar"}, + {[]string{"foo.", ".bar"}, "foo...bar"}, + } + + for _, tt := range tests { + withLogger(t, DebugLevel, nil, func(log *Logger, logs *observer.ObservedLogs) { + for _, n := range tt.names { + log = log.Named(n) + } + log.Info("") + require.Equal(t, 1, logs.Len(), "Expected only one log entry to be written.") + assert.Equal(t, tt.expected, logs.AllUntimed()[0].LoggerName, "Unexpected logger name from entry.") + assert.Equal(t, tt.expected, log.Name(), "Unexpected logger name.") + }) + withSugar(t, DebugLevel, nil, func(log *SugaredLogger, logs *observer.ObservedLogs) { + for _, n := range tt.names { + log = log.Named(n) + } + log.Infow("") + require.Equal(t, 1, logs.Len(), "Expected only one log entry to be written.") + assert.Equal(t, tt.expected, logs.AllUntimed()[0].LoggerName, "Unexpected logger name from entry.") + assert.Equal(t, tt.expected, log.base.Name(), "Unexpected logger name.") + }) + } +} + +func TestLoggerWriteFailure(t *testing.T) { + errSink := &ztest.Buffer{} + logger := New( + zapcore.NewCore( + zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig), + zapcore.Lock(zapcore.AddSync(ztest.FailWriter{})), + DebugLevel, + ), + ErrorOutput(errSink), + ) + + logger.Info("foo") + // Should log the error. + assert.Regexp(t, `write error: failed`, errSink.Stripped(), "Expected to log the error to the error output.") + assert.True(t, errSink.Called(), "Expected logging an internal error to call Sync the error sink.") +} + +func TestLoggerSync(t *testing.T) { + withLogger(t, DebugLevel, nil, func(logger *Logger, _ *observer.ObservedLogs) { + assert.NoError(t, logger.Sync(), "Expected syncing a test logger to succeed.") + assert.NoError(t, logger.Sugar().Sync(), "Expected syncing a sugared logger to succeed.") + }) +} + +func TestLoggerSyncFail(t *testing.T) { + noSync := &ztest.Buffer{} + err := errors.New("fail") + noSync.SetError(err) + logger := New(zapcore.NewCore( + zapcore.NewJSONEncoder(zapcore.EncoderConfig{}), + noSync, + DebugLevel, + )) + assert.Equal(t, err, logger.Sync(), "Expected Logger.Sync to propagate errors.") + assert.Equal(t, err, logger.Sugar().Sync(), "Expected SugaredLogger.Sync to propagate errors.") +} + +func TestLoggerAddCaller(t *testing.T) { + tests := []struct { + options []Option + pat string + }{ + {opts(), `^undefined$`}, + {opts(WithCaller(false)), `^undefined$`}, + {opts(AddCaller()), `.+/logger_test.go:[\d]+$`}, + {opts(AddCaller(), WithCaller(false)), `^undefined$`}, + {opts(WithCaller(true)), `.+/logger_test.go:[\d]+$`}, + {opts(WithCaller(true), WithCaller(false)), `^undefined$`}, + {opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(-1)), `.+/logger_test.go:[\d]+$`}, + {opts(AddCaller(), AddCallerSkip(1)), `.+/common_test.go:[\d]+$`}, + {opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(3)), `.+/src/runtime/.*:[\d]+$`}, + } + for _, tt := range tests { + withLogger(t, DebugLevel, tt.options, func(logger *Logger, logs *observer.ObservedLogs) { + // Make sure that sugaring and desugaring resets caller skip properly. + logger = logger.Sugar().Desugar() + logger.Info("") + output := logs.AllUntimed() + assert.Equal(t, 1, len(output), "Unexpected number of logs written out.") + assert.Regexp( + t, + tt.pat, + output[0].Caller, + "Expected to find package name and file name in output.", + ) + }) + } +} + +func TestLoggerAddCallerFunction(t *testing.T) { + tests := []struct { + options []Option + loggerFunction string + sugaredFunction string + }{ + { + options: opts(), + loggerFunction: "", + sugaredFunction: "", + }, + { + options: opts(WithCaller(false)), + loggerFunction: "", + sugaredFunction: "", + }, + { + options: opts(AddCaller()), + loggerFunction: "go.uber.org/zap.infoLog", + sugaredFunction: "go.uber.org/zap.infoLogSugared", + }, + { + options: opts(AddCaller(), WithCaller(false)), + loggerFunction: "", + sugaredFunction: "", + }, + { + options: opts(WithCaller(true)), + loggerFunction: "go.uber.org/zap.infoLog", + sugaredFunction: "go.uber.org/zap.infoLogSugared", + }, + { + options: opts(WithCaller(true), WithCaller(false)), + loggerFunction: "", + sugaredFunction: "", + }, + { + options: opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(-1)), + loggerFunction: "go.uber.org/zap.infoLog", + sugaredFunction: "go.uber.org/zap.infoLogSugared", + }, + { + options: opts(AddCaller(), AddCallerSkip(2)), + loggerFunction: "go.uber.org/zap.withLogger", + sugaredFunction: "go.uber.org/zap.withLogger", + }, + { + options: opts(AddCaller(), AddCallerSkip(2), AddCallerSkip(3)), + loggerFunction: "runtime.goexit", + sugaredFunction: "runtime.goexit", + }, + } + for _, tt := range tests { + withLogger(t, DebugLevel, tt.options, func(logger *Logger, logs *observer.ObservedLogs) { + // Make sure that sugaring and desugaring resets caller skip properly. + logger = logger.Sugar().Desugar() + infoLog(logger, "") + infoLogSugared(logger.Sugar(), "") + infoLog(logger.Sugar().Desugar(), "") + + entries := logs.AllUntimed() + assert.Equal(t, 3, len(entries), "Unexpected number of logs written out.") + for _, entry := range []observer.LoggedEntry{entries[0], entries[2]} { + assert.Regexp( + t, + tt.loggerFunction, + entry.Caller.Function, + "Expected to find function name in output.", + ) + } + assert.Regexp( + t, + tt.sugaredFunction, + entries[1].Caller.Function, + "Expected to find function name in output.", + ) + }) + } +} + +func TestLoggerAddCallerFail(t *testing.T) { + errBuf := &ztest.Buffer{} + withLogger(t, DebugLevel, opts(AddCaller(), AddCallerSkip(1e3), ErrorOutput(errBuf)), func(log *Logger, logs *observer.ObservedLogs) { + log.Info("Failure.") + assert.Regexp( + t, + `Logger.check error: failed to get caller`, + errBuf.String(), + "Didn't find expected failure message.", + ) + assert.Equal( + t, + logs.AllUntimed()[0].Message, + "Failure.", + "Expected original message to survive failures in runtime.Caller.") + assert.Equal( + t, + logs.AllUntimed()[0].Caller.Function, + "", + "Expected function name to be empty string.") + }) +} + +func TestLoggerReplaceCore(t *testing.T) { + replace := WrapCore(func(zapcore.Core) zapcore.Core { + return zapcore.NewNopCore() + }) + withLogger(t, DebugLevel, opts(replace), func(logger *Logger, logs *observer.ObservedLogs) { + logger.Debug("") + logger.Info("") + logger.Warn("") + assert.Equal(t, 0, logs.Len(), "Expected no-op core to write no logs.") + }) +} + +func TestLoggerIncreaseLevel(t *testing.T) { + withLogger(t, DebugLevel, opts(IncreaseLevel(WarnLevel)), func(logger *Logger, logs *observer.ObservedLogs) { + logger.Info("logger.Info") + logger.Warn("logger.Warn") + logger.Error("logger.Error") + require.Equal(t, 2, logs.Len(), "expected only warn + error logs due to IncreaseLevel.") + assert.Equal( + t, + logs.AllUntimed()[0].Message, + "logger.Warn", + "Expected first logged message to be warn level message", + ) + }) +} + +func TestLoggerHooks(t *testing.T) { + hook, seen := makeCountingHook() + withLogger(t, DebugLevel, opts(Hooks(hook)), func(logger *Logger, logs *observer.ObservedLogs) { + logger.Debug("") + logger.Info("") + }) + assert.Equal(t, int64(2), seen.Load(), "Hook saw an unexpected number of logs.") +} + +func TestLoggerConcurrent(t *testing.T) { + withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) { + child := logger.With(String("foo", "bar")) + + wg := &sync.WaitGroup{} + runConcurrently(5, 10, wg, func() { + logger.Info("", String("foo", "bar")) + }) + runConcurrently(5, 10, wg, func() { + child.Info("") + }) + + wg.Wait() + + // Make sure the output doesn't contain interspersed entries. + assert.Equal(t, 100, logs.Len(), "Unexpected number of logs written out.") + for _, obs := range logs.AllUntimed() { + assert.Equal( + t, + observer.LoggedEntry{ + Entry: zapcore.Entry{Level: InfoLevel}, + Context: []Field{String("foo", "bar")}, + }, + obs, + "Unexpected log output.", + ) + } + }) +} + +func TestLoggerFatalOnNoop(t *testing.T) { + exitStub := exit.Stub() + defer exitStub.Unstub() + core, _ := observer.New(InfoLevel) + + // We don't allow a no-op fatal hook. + New(core, WithFatalHook(zapcore.WriteThenNoop)).Fatal("great sadness") + assert.True(t, exitStub.Exited, "must exit for WriteThenNoop") + assert.Equal(t, 1, exitStub.Code, "must exit with status 1 for WriteThenNoop") +} + +func TestLoggerCustomOnFatal(t *testing.T) { + tests := []struct { + msg string + onFatal zapcore.CheckWriteAction + recoverValue interface{} + }{ + { + msg: "panic", + onFatal: zapcore.WriteThenPanic, + recoverValue: "fatal", + }, + { + msg: "goexit", + onFatal: zapcore.WriteThenGoexit, + recoverValue: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.msg, func(t *testing.T) { + withLogger(t, InfoLevel, opts(OnFatal(tt.onFatal)), func(logger *Logger, logs *observer.ObservedLogs) { + var finished bool + recovered := make(chan interface{}) + go func() { + defer func() { + recovered <- recover() + }() + + logger.Fatal("fatal") + finished = true + }() + + assert.Equal(t, tt.recoverValue, <-recovered, "unexpected value from recover()") + assert.False(t, finished, "expect goroutine to not finish after Fatal") + + assert.Equal(t, []observer.LoggedEntry{{ + Entry: zapcore.Entry{Level: FatalLevel, Message: "fatal"}, + Context: []Field{}, + }}, logs.AllUntimed(), "unexpected logs") + }) + }) + } +} + +type customWriteHook struct { + called bool +} + +func (h *customWriteHook) OnWrite(_ *zapcore.CheckedEntry, _ []Field) { + h.called = true +} + +func TestLoggerWithFatalHook(t *testing.T) { + var h customWriteHook + withLogger(t, InfoLevel, opts(WithFatalHook(&h)), func(logger *Logger, logs *observer.ObservedLogs) { + logger.Fatal("great sadness") + assert.True(t, h.called) + assert.Equal(t, 1, logs.FilterLevelExact(FatalLevel).Len()) + }) +} + +func TestNopLogger(t *testing.T) { + logger := NewNop() + + t.Run("basic levels", func(t *testing.T) { + logger.Debug("foo", String("k", "v")) + logger.Info("bar", Int("x", 42)) + logger.Warn("baz", Strings("ks", []string{"a", "b"})) + logger.Error("qux", Error(errors.New("great sadness"))) + }) + + t.Run("DPanic", func(t *testing.T) { + logger.With(String("component", "whatever")).DPanic("stuff") + }) + + t.Run("Panic", func(t *testing.T) { + assert.Panics(t, func() { + logger.Panic("great sadness") + }, "Nop logger should still cause panics.") + }) +} + +func TestMust(t *testing.T) { + t.Run("must without an error does not panic", func(t *testing.T) { + assert.NotPanics(t, func() { Must(NewNop(), nil) }, "must paniced with no error") + }) + + t.Run("must with an error panics", func(t *testing.T) { + assert.Panics(t, func() { Must(nil, errors.New("an error")) }, "must did not panic with an error") + }) +} + +func infoLog(logger *Logger, msg string, fields ...Field) { + logger.Info(msg, fields...) +} + +func infoLogSugared(logger *SugaredLogger, args ...interface{}) { + logger.Info(args...) +} diff --git a/vendor/go.uber.org/zap/options.go b/vendor/go.uber.org/zap/options.go new file mode 100644 index 0000000000..c4f3bca3d2 --- /dev/null +++ b/vendor/go.uber.org/zap/options.go @@ -0,0 +1,167 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "fmt" + + "go.uber.org/zap/zapcore" +) + +// An Option configures a Logger. +type Option interface { + apply(*Logger) +} + +// optionFunc wraps a func so it satisfies the Option interface. +type optionFunc func(*Logger) + +func (f optionFunc) apply(log *Logger) { + f(log) +} + +// WrapCore wraps or replaces the Logger's underlying zapcore.Core. +func WrapCore(f func(zapcore.Core) zapcore.Core) Option { + return optionFunc(func(log *Logger) { + log.core = f(log.core) + }) +} + +// Hooks registers functions which will be called each time the Logger writes +// out an Entry. Repeated use of Hooks is additive. +// +// Hooks are useful for simple side effects, like capturing metrics for the +// number of emitted logs. More complex side effects, including anything that +// requires access to the Entry's structured fields, should be implemented as +// a zapcore.Core instead. See zapcore.RegisterHooks for details. +func Hooks(hooks ...func(zapcore.Entry) error) Option { + return optionFunc(func(log *Logger) { + log.core = zapcore.RegisterHooks(log.core, hooks...) + }) +} + +// Fields adds fields to the Logger. +func Fields(fs ...Field) Option { + return optionFunc(func(log *Logger) { + log.core = log.core.With(fs) + }) +} + +// ErrorOutput sets the destination for errors generated by the Logger. Note +// that this option only affects internal errors; for sample code that sends +// error-level logs to a different location from info- and debug-level logs, +// see the package-level AdvancedConfiguration example. +// +// The supplied WriteSyncer must be safe for concurrent use. The Open and +// zapcore.Lock functions are the simplest ways to protect files with a mutex. +func ErrorOutput(w zapcore.WriteSyncer) Option { + return optionFunc(func(log *Logger) { + log.errorOutput = w + }) +} + +// Development puts the logger in development mode, which makes DPanic-level +// logs panic instead of simply logging an error. +func Development() Option { + return optionFunc(func(log *Logger) { + log.development = true + }) +} + +// AddCaller configures the Logger to annotate each message with the filename, +// line number, and function name of zap's caller. See also WithCaller. +func AddCaller() Option { + return WithCaller(true) +} + +// WithCaller configures the Logger to annotate each message with the filename, +// line number, and function name of zap's caller, or not, depending on the +// value of enabled. This is a generalized form of AddCaller. +func WithCaller(enabled bool) Option { + return optionFunc(func(log *Logger) { + log.addCaller = enabled + }) +} + +// AddCallerSkip increases the number of callers skipped by caller annotation +// (as enabled by the AddCaller option). When building wrappers around the +// Logger and SugaredLogger, supplying this Option prevents zap from always +// reporting the wrapper code as the caller. +func AddCallerSkip(skip int) Option { + return optionFunc(func(log *Logger) { + log.callerSkip += skip + }) +} + +// AddStacktrace configures the Logger to record a stack trace for all messages at +// or above a given level. +func AddStacktrace(lvl zapcore.LevelEnabler) Option { + return optionFunc(func(log *Logger) { + log.addStack = lvl + }) +} + +// IncreaseLevel increase the level of the logger. It has no effect if +// the passed in level tries to decrease the level of the logger. +func IncreaseLevel(lvl zapcore.LevelEnabler) Option { + return optionFunc(func(log *Logger) { + core, err := zapcore.NewIncreaseLevelCore(log.core, lvl) + if err != nil { + fmt.Fprintf(log.errorOutput, "failed to IncreaseLevel: %v\n", err) + } else { + log.core = core + } + }) +} + +// OnFatal sets the action to take on fatal logs. +// +// Deprecated: Use [WithFatalHook] instead. +func OnFatal(action zapcore.CheckWriteAction) Option { + return WithFatalHook(action) +} + +// WithFatalHook sets a CheckWriteHook to run on fatal logs. +// Zap will call this hook after writing a log statement with a Fatal level. +// +// For example, the following builds a logger that will exit the current +// goroutine after writing a fatal log message, but it will not exit the +// program. +// +// zap.New(core, zap.WithFatalHook(zapcore.WriteThenGoexit)) +// +// It is important that the provided CheckWriteHook stops the control flow at +// the current statement to meet expectations of callers of the logger. +// We recommend calling os.Exit or runtime.Goexit inside custom hooks at +// minimum. +func WithFatalHook(hook zapcore.CheckWriteHook) Option { + return optionFunc(func(log *Logger) { + log.onFatal = hook + }) +} + +// WithClock specifies the clock used by the logger to determine the current +// time for logged entries. Defaults to the system clock with time.Now. +func WithClock(clock zapcore.Clock) Option { + return optionFunc(func(log *Logger) { + log.clock = clock + }) +} diff --git a/vendor/go.uber.org/zap/sink.go b/vendor/go.uber.org/zap/sink.go new file mode 100644 index 0000000000..499772a00d --- /dev/null +++ b/vendor/go.uber.org/zap/sink.go @@ -0,0 +1,180 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "fmt" + "io" + "net/url" + "os" + "path/filepath" + "strings" + "sync" + + "go.uber.org/zap/zapcore" +) + +const schemeFile = "file" + +var _sinkRegistry = newSinkRegistry() + +// Sink defines the interface to write to and close logger destinations. +type Sink interface { + zapcore.WriteSyncer + io.Closer +} + +type errSinkNotFound struct { + scheme string +} + +func (e *errSinkNotFound) Error() string { + return fmt.Sprintf("no sink found for scheme %q", e.scheme) +} + +type nopCloserSink struct{ zapcore.WriteSyncer } + +func (nopCloserSink) Close() error { return nil } + +type sinkRegistry struct { + mu sync.Mutex + factories map[string]func(*url.URL) (Sink, error) // keyed by scheme + openFile func(string, int, os.FileMode) (*os.File, error) // type matches os.OpenFile +} + +func newSinkRegistry() *sinkRegistry { + sr := &sinkRegistry{ + factories: make(map[string]func(*url.URL) (Sink, error)), + openFile: os.OpenFile, + } + // Infallible operation: the registry is empty, so we can't have a conflict. + _ = sr.RegisterSink(schemeFile, sr.newFileSinkFromURL) + return sr +} + +// RegisterScheme registers the given factory for the specific scheme. +func (sr *sinkRegistry) RegisterSink(scheme string, factory func(*url.URL) (Sink, error)) error { + sr.mu.Lock() + defer sr.mu.Unlock() + + if scheme == "" { + return errors.New("can't register a sink factory for empty string") + } + normalized, err := normalizeScheme(scheme) + if err != nil { + return fmt.Errorf("%q is not a valid scheme: %v", scheme, err) + } + if _, ok := sr.factories[normalized]; ok { + return fmt.Errorf("sink factory already registered for scheme %q", normalized) + } + sr.factories[normalized] = factory + return nil +} + +func (sr *sinkRegistry) newSink(rawURL string) (Sink, error) { + // URL parsing doesn't work well for Windows paths such as `c:\log.txt`, as scheme is set to + // the drive, and path is unset unless `c:/log.txt` is used. + // To avoid Windows-specific URL handling, we instead check IsAbs to open as a file. + // filepath.IsAbs is OS-specific, so IsAbs('c:/log.txt') is false outside of Windows. + if filepath.IsAbs(rawURL) { + return sr.newFileSinkFromPath(rawURL) + } + + u, err := url.Parse(rawURL) + if err != nil { + return nil, fmt.Errorf("can't parse %q as a URL: %v", rawURL, err) + } + if u.Scheme == "" { + u.Scheme = schemeFile + } + + sr.mu.Lock() + factory, ok := sr.factories[u.Scheme] + sr.mu.Unlock() + if !ok { + return nil, &errSinkNotFound{u.Scheme} + } + return factory(u) +} + +// RegisterSink registers a user-supplied factory for all sinks with a +// particular scheme. +// +// All schemes must be ASCII, valid under section 0.1 of RFC 3986 +// (https://tools.ietf.org/html/rfc3983#section-3.1), and must not already +// have a factory registered. Zap automatically registers a factory for the +// "file" scheme. +func RegisterSink(scheme string, factory func(*url.URL) (Sink, error)) error { + return _sinkRegistry.RegisterSink(scheme, factory) +} + +func (sr *sinkRegistry) newFileSinkFromURL(u *url.URL) (Sink, error) { + if u.User != nil { + return nil, fmt.Errorf("user and password not allowed with file URLs: got %v", u) + } + if u.Fragment != "" { + return nil, fmt.Errorf("fragments not allowed with file URLs: got %v", u) + } + if u.RawQuery != "" { + return nil, fmt.Errorf("query parameters not allowed with file URLs: got %v", u) + } + // Error messages are better if we check hostname and port separately. + if u.Port() != "" { + return nil, fmt.Errorf("ports not allowed with file URLs: got %v", u) + } + if hn := u.Hostname(); hn != "" && hn != "localhost" { + return nil, fmt.Errorf("file URLs must leave host empty or use localhost: got %v", u) + } + + return sr.newFileSinkFromPath(u.Path) +} + +func (sr *sinkRegistry) newFileSinkFromPath(path string) (Sink, error) { + switch path { + case "stdout": + return nopCloserSink{os.Stdout}, nil + case "stderr": + return nopCloserSink{os.Stderr}, nil + } + return sr.openFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o666) +} + +func normalizeScheme(s string) (string, error) { + // https://tools.ietf.org/html/rfc3986#section-3.1 + s = strings.ToLower(s) + if first := s[0]; 'a' > first || 'z' < first { + return "", errors.New("must start with a letter") + } + for i := 1; i < len(s); i++ { // iterate over bytes, not runes + c := s[i] + switch { + case 'a' <= c && c <= 'z': + continue + case '0' <= c && c <= '9': + continue + case c == '.' || c == '+' || c == '-': + continue + } + return "", fmt.Errorf("may not contain %q", c) + } + return s, nil +} diff --git a/vendor/go.uber.org/zap/sink_test.go b/vendor/go.uber.org/zap/sink_test.go new file mode 100644 index 0000000000..5fc37be918 --- /dev/null +++ b/vendor/go.uber.org/zap/sink_test.go @@ -0,0 +1,107 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "bytes" + "io" + "net/url" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.uber.org/zap/zapcore" +) + +func stubSinkRegistry(t testing.TB) *sinkRegistry { + origSinkRegistry := _sinkRegistry + t.Cleanup(func() { + _sinkRegistry = origSinkRegistry + }) + + r := newSinkRegistry() + _sinkRegistry = r + return r +} + +func TestRegisterSink(t *testing.T) { + stubSinkRegistry(t) + + const ( + memScheme = "mem" + nopScheme = "no-op.1234" + ) + var memCalls, nopCalls int + + buf := bytes.NewBuffer(nil) + memFactory := func(u *url.URL) (Sink, error) { + assert.Equal(t, u.Scheme, memScheme, "Scheme didn't match registration.") + memCalls++ + return nopCloserSink{zapcore.AddSync(buf)}, nil + } + nopFactory := func(u *url.URL) (Sink, error) { + assert.Equal(t, u.Scheme, nopScheme, "Scheme didn't match registration.") + nopCalls++ + return nopCloserSink{zapcore.AddSync(io.Discard)}, nil + } + + require.NoError(t, RegisterSink(strings.ToUpper(memScheme), memFactory), "Failed to register scheme %q.", memScheme) + require.NoError(t, RegisterSink(nopScheme, nopFactory), "Failed to register scheme %q.", nopScheme) + + sink, closeSink, err := Open( + memScheme+"://somewhere", + nopScheme+"://somewhere-else", + ) + require.NoError(t, err, "Unexpected error opening URLs with registered schemes.") + defer closeSink() + + assert.Equal(t, 1, memCalls, "Unexpected number of calls to memory factory.") + assert.Equal(t, 1, nopCalls, "Unexpected number of calls to no-op factory.") + + _, err = sink.Write([]byte("foo")) + assert.NoError(t, err, "Failed to write to combined WriteSyncer.") + assert.Equal(t, "foo", buf.String(), "Unexpected buffer contents.") +} + +func TestRegisterSinkErrors(t *testing.T) { + nopFactory := func(_ *url.URL) (Sink, error) { + return nopCloserSink{zapcore.AddSync(io.Discard)}, nil + } + tests := []struct { + scheme string + err string + }{ + {"", "empty string"}, + {"FILE", "already registered"}, + {"42", "not a valid scheme"}, + {"http*", "not a valid scheme"}, + } + + for _, tt := range tests { + t.Run("scheme-"+tt.scheme, func(t *testing.T) { + r := newSinkRegistry() + err := r.RegisterSink(tt.scheme, nopFactory) + assert.ErrorContains(t, err, tt.err) + }) + } +} diff --git a/vendor/go.uber.org/zap/sink_windows_test.go b/vendor/go.uber.org/zap/sink_windows_test.go new file mode 100644 index 0000000000..fd6a475955 --- /dev/null +++ b/vendor/go.uber.org/zap/sink_windows_test.go @@ -0,0 +1,71 @@ +// Copyright (c) 2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//go:build windows + +package zap + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWindowsPaths(t *testing.T) { + // See https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats + tests := []struct { + msg string + path string + }{ + { + msg: "local path with drive", + path: `c:\log.json`, + }, + { + msg: "local path with drive using forward slash", + path: `c:/log.json`, + }, + { + msg: "local path without drive", + path: `\Temp\log.json`, + }, + { + msg: "unc path", + path: `\\Server2\Logs\log.json`, + }, + } + + for _, tt := range tests { + t.Run(tt.msg, func(t *testing.T) { + sr := newSinkRegistry() + + openFilename := "<not called>" + sr.openFile = func(filename string, _ int, _ os.FileMode) (*os.File, error) { + openFilename = filename + return nil, assert.AnError + } + + _, err := sr.newSink(tt.path) + assert.Equal(t, assert.AnError, err, "expect stub error from OpenFile") + assert.Equal(t, tt.path, openFilename, "unexpected path opened") + }) + } +} diff --git a/vendor/go.uber.org/zap/sugar.go b/vendor/go.uber.org/zap/sugar.go new file mode 100644 index 0000000000..00ac5fe3ac --- /dev/null +++ b/vendor/go.uber.org/zap/sugar.go @@ -0,0 +1,437 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "fmt" + + "go.uber.org/zap/zapcore" + + "go.uber.org/multierr" +) + +const ( + _oddNumberErrMsg = "Ignored key without a value." + _nonStringKeyErrMsg = "Ignored key-value pairs with non-string keys." + _multipleErrMsg = "Multiple errors without a key." +) + +// A SugaredLogger wraps the base Logger functionality in a slower, but less +// verbose, API. Any Logger can be converted to a SugaredLogger with its Sugar +// method. +// +// Unlike the Logger, the SugaredLogger doesn't insist on structured logging. +// For each log level, it exposes four methods: +// +// - methods named after the log level for log.Print-style logging +// - methods ending in "w" for loosely-typed structured logging +// - methods ending in "f" for log.Printf-style logging +// - methods ending in "ln" for log.Println-style logging +// +// For example, the methods for InfoLevel are: +// +// Info(...any) Print-style logging +// Infow(...any) Structured logging (read as "info with") +// Infof(string, ...any) Printf-style logging +// Infoln(...any) Println-style logging +type SugaredLogger struct { + base *Logger +} + +// Desugar unwraps a SugaredLogger, exposing the original Logger. Desugaring +// is quite inexpensive, so it's reasonable for a single application to use +// both Loggers and SugaredLoggers, converting between them on the boundaries +// of performance-sensitive code. +func (s *SugaredLogger) Desugar() *Logger { + base := s.base.clone() + base.callerSkip -= 2 + return base +} + +// Named adds a sub-scope to the logger's name. See Logger.Named for details. +func (s *SugaredLogger) Named(name string) *SugaredLogger { + return &SugaredLogger{base: s.base.Named(name)} +} + +// WithOptions clones the current SugaredLogger, applies the supplied Options, +// and returns the result. It's safe to use concurrently. +func (s *SugaredLogger) WithOptions(opts ...Option) *SugaredLogger { + base := s.base.clone() + for _, opt := range opts { + opt.apply(base) + } + return &SugaredLogger{base: base} +} + +// With adds a variadic number of fields to the logging context. It accepts a +// mix of strongly-typed Field objects and loosely-typed key-value pairs. When +// processing pairs, the first element of the pair is used as the field key +// and the second as the field value. +// +// For example, +// +// sugaredLogger.With( +// "hello", "world", +// "failure", errors.New("oh no"), +// Stack(), +// "count", 42, +// "user", User{Name: "alice"}, +// ) +// +// is the equivalent of +// +// unsugared.With( +// String("hello", "world"), +// String("failure", "oh no"), +// Stack(), +// Int("count", 42), +// Object("user", User{Name: "alice"}), +// ) +// +// Note that the keys in key-value pairs should be strings. In development, +// passing a non-string key panics. In production, the logger is more +// forgiving: a separate error is logged, but the key-value pair is skipped +// and execution continues. Passing an orphaned key triggers similar behavior: +// panics in development and errors in production. +func (s *SugaredLogger) With(args ...interface{}) *SugaredLogger { + return &SugaredLogger{base: s.base.With(s.sweetenFields(args)...)} +} + +// Level reports the minimum enabled level for this logger. +// +// For NopLoggers, this is [zapcore.InvalidLevel]. +func (s *SugaredLogger) Level() zapcore.Level { + return zapcore.LevelOf(s.base.core) +} + +// Debug logs the provided arguments at [DebugLevel]. +// Spaces are added between arguments when neither is a string. +func (s *SugaredLogger) Debug(args ...interface{}) { + s.log(DebugLevel, "", args, nil) +} + +// Info logs the provided arguments at [InfoLevel]. +// Spaces are added between arguments when neither is a string. +func (s *SugaredLogger) Info(args ...interface{}) { + s.log(InfoLevel, "", args, nil) +} + +// Warn logs the provided arguments at [WarnLevel]. +// Spaces are added between arguments when neither is a string. +func (s *SugaredLogger) Warn(args ...interface{}) { + s.log(WarnLevel, "", args, nil) +} + +// Error logs the provided arguments at [ErrorLevel]. +// Spaces are added between arguments when neither is a string. +func (s *SugaredLogger) Error(args ...interface{}) { + s.log(ErrorLevel, "", args, nil) +} + +// DPanic logs the provided arguments at [DPanicLevel]. +// In development, the logger then panics. (See [DPanicLevel] for details.) +// Spaces are added between arguments when neither is a string. +func (s *SugaredLogger) DPanic(args ...interface{}) { + s.log(DPanicLevel, "", args, nil) +} + +// Panic constructs a message with the provided arguments and panics. +// Spaces are added between arguments when neither is a string. +func (s *SugaredLogger) Panic(args ...interface{}) { + s.log(PanicLevel, "", args, nil) +} + +// Fatal constructs a message with the provided arguments and calls os.Exit. +// Spaces are added between arguments when neither is a string. +func (s *SugaredLogger) Fatal(args ...interface{}) { + s.log(FatalLevel, "", args, nil) +} + +// Debugf formats the message according to the format specifier +// and logs it at [DebugLevel]. +func (s *SugaredLogger) Debugf(template string, args ...interface{}) { + s.log(DebugLevel, template, args, nil) +} + +// Infof formats the message according to the format specifier +// and logs it at [InfoLevel]. +func (s *SugaredLogger) Infof(template string, args ...interface{}) { + s.log(InfoLevel, template, args, nil) +} + +// Warnf formats the message according to the format specifier +// and logs it at [WarnLevel]. +func (s *SugaredLogger) Warnf(template string, args ...interface{}) { + s.log(WarnLevel, template, args, nil) +} + +// Errorf formats the message according to the format specifier +// and logs it at [ErrorLevel]. +func (s *SugaredLogger) Errorf(template string, args ...interface{}) { + s.log(ErrorLevel, template, args, nil) +} + +// DPanicf formats the message according to the format specifier +// and logs it at [DPanicLevel]. +// In development, the logger then panics. (See [DPanicLevel] for details.) +func (s *SugaredLogger) DPanicf(template string, args ...interface{}) { + s.log(DPanicLevel, template, args, nil) +} + +// Panicf formats the message according to the format specifier +// and panics. +func (s *SugaredLogger) Panicf(template string, args ...interface{}) { + s.log(PanicLevel, template, args, nil) +} + +// Fatalf formats the message according to the format specifier +// and calls os.Exit. +func (s *SugaredLogger) Fatalf(template string, args ...interface{}) { + s.log(FatalLevel, template, args, nil) +} + +// Debugw logs a message with some additional context. The variadic key-value +// pairs are treated as they are in With. +// +// When debug-level logging is disabled, this is much faster than +// +// s.With(keysAndValues).Debug(msg) +func (s *SugaredLogger) Debugw(msg string, keysAndValues ...interface{}) { + s.log(DebugLevel, msg, nil, keysAndValues) +} + +// Infow logs a message with some additional context. The variadic key-value +// pairs are treated as they are in With. +func (s *SugaredLogger) Infow(msg string, keysAndValues ...interface{}) { + s.log(InfoLevel, msg, nil, keysAndValues) +} + +// Warnw logs a message with some additional context. The variadic key-value +// pairs are treated as they are in With. +func (s *SugaredLogger) Warnw(msg string, keysAndValues ...interface{}) { + s.log(WarnLevel, msg, nil, keysAndValues) +} + +// Errorw logs a message with some additional context. The variadic key-value +// pairs are treated as they are in With. +func (s *SugaredLogger) Errorw(msg string, keysAndValues ...interface{}) { + s.log(ErrorLevel, msg, nil, keysAndValues) +} + +// DPanicw logs a message with some additional context. In development, the +// logger then panics. (See DPanicLevel for details.) The variadic key-value +// pairs are treated as they are in With. +func (s *SugaredLogger) DPanicw(msg string, keysAndValues ...interface{}) { + s.log(DPanicLevel, msg, nil, keysAndValues) +} + +// Panicw logs a message with some additional context, then panics. The +// variadic key-value pairs are treated as they are in With. +func (s *SugaredLogger) Panicw(msg string, keysAndValues ...interface{}) { + s.log(PanicLevel, msg, nil, keysAndValues) +} + +// Fatalw logs a message with some additional context, then calls os.Exit. The +// variadic key-value pairs are treated as they are in With. +func (s *SugaredLogger) Fatalw(msg string, keysAndValues ...interface{}) { + s.log(FatalLevel, msg, nil, keysAndValues) +} + +// Debugln logs a message at [DebugLevel]. +// Spaces are always added between arguments. +func (s *SugaredLogger) Debugln(args ...interface{}) { + s.logln(DebugLevel, args, nil) +} + +// Infoln logs a message at [InfoLevel]. +// Spaces are always added between arguments. +func (s *SugaredLogger) Infoln(args ...interface{}) { + s.logln(InfoLevel, args, nil) +} + +// Warnln logs a message at [WarnLevel]. +// Spaces are always added between arguments. +func (s *SugaredLogger) Warnln(args ...interface{}) { + s.logln(WarnLevel, args, nil) +} + +// Errorln logs a message at [ErrorLevel]. +// Spaces are always added between arguments. +func (s *SugaredLogger) Errorln(args ...interface{}) { + s.logln(ErrorLevel, args, nil) +} + +// DPanicln logs a message at [DPanicLevel]. +// In development, the logger then panics. (See [DPanicLevel] for details.) +// Spaces are always added between arguments. +func (s *SugaredLogger) DPanicln(args ...interface{}) { + s.logln(DPanicLevel, args, nil) +} + +// Panicln logs a message at [PanicLevel] and panics. +// Spaces are always added between arguments. +func (s *SugaredLogger) Panicln(args ...interface{}) { + s.logln(PanicLevel, args, nil) +} + +// Fatalln logs a message at [FatalLevel] and calls os.Exit. +// Spaces are always added between arguments. +func (s *SugaredLogger) Fatalln(args ...interface{}) { + s.logln(FatalLevel, args, nil) +} + +// Sync flushes any buffered log entries. +func (s *SugaredLogger) Sync() error { + return s.base.Sync() +} + +// log message with Sprint, Sprintf, or neither. +func (s *SugaredLogger) log(lvl zapcore.Level, template string, fmtArgs []interface{}, context []interface{}) { + // If logging at this level is completely disabled, skip the overhead of + // string formatting. + if lvl < DPanicLevel && !s.base.Core().Enabled(lvl) { + return + } + + msg := getMessage(template, fmtArgs) + if ce := s.base.Check(lvl, msg); ce != nil { + ce.Write(s.sweetenFields(context)...) + } +} + +// logln message with Sprintln +func (s *SugaredLogger) logln(lvl zapcore.Level, fmtArgs []interface{}, context []interface{}) { + if lvl < DPanicLevel && !s.base.Core().Enabled(lvl) { + return + } + + msg := getMessageln(fmtArgs) + if ce := s.base.Check(lvl, msg); ce != nil { + ce.Write(s.sweetenFields(context)...) + } +} + +// getMessage format with Sprint, Sprintf, or neither. +func getMessage(template string, fmtArgs []interface{}) string { + if len(fmtArgs) == 0 { + return template + } + + if template != "" { + return fmt.Sprintf(template, fmtArgs...) + } + + if len(fmtArgs) == 1 { + if str, ok := fmtArgs[0].(string); ok { + return str + } + } + return fmt.Sprint(fmtArgs...) +} + +// getMessageln format with Sprintln. +func getMessageln(fmtArgs []interface{}) string { + msg := fmt.Sprintln(fmtArgs...) + return msg[:len(msg)-1] +} + +func (s *SugaredLogger) sweetenFields(args []interface{}) []Field { + if len(args) == 0 { + return nil + } + + var ( + // Allocate enough space for the worst case; if users pass only structured + // fields, we shouldn't penalize them with extra allocations. + fields = make([]Field, 0, len(args)) + invalid invalidPairs + seenError bool + ) + + for i := 0; i < len(args); { + // This is a strongly-typed field. Consume it and move on. + if f, ok := args[i].(Field); ok { + fields = append(fields, f) + i++ + continue + } + + // If it is an error, consume it and move on. + if err, ok := args[i].(error); ok { + if !seenError { + seenError = true + fields = append(fields, Error(err)) + } else { + s.base.Error(_multipleErrMsg, Error(err)) + } + i++ + continue + } + + // Make sure this element isn't a dangling key. + if i == len(args)-1 { + s.base.Error(_oddNumberErrMsg, Any("ignored", args[i])) + break + } + + // Consume this value and the next, treating them as a key-value pair. If the + // key isn't a string, add this pair to the slice of invalid pairs. + key, val := args[i], args[i+1] + if keyStr, ok := key.(string); !ok { + // Subsequent errors are likely, so allocate once up front. + if cap(invalid) == 0 { + invalid = make(invalidPairs, 0, len(args)/2) + } + invalid = append(invalid, invalidPair{i, key, val}) + } else { + fields = append(fields, Any(keyStr, val)) + } + i += 2 + } + + // If we encountered any invalid key-value pairs, log an error. + if len(invalid) > 0 { + s.base.Error(_nonStringKeyErrMsg, Array("invalid", invalid)) + } + return fields +} + +type invalidPair struct { + position int + key, value interface{} +} + +func (p invalidPair) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddInt64("position", int64(p.position)) + Any("key", p.key).AddTo(enc) + Any("value", p.value).AddTo(enc) + return nil +} + +type invalidPairs []invalidPair + +func (ps invalidPairs) MarshalLogArray(enc zapcore.ArrayEncoder) error { + var err error + for i := range ps { + err = multierr.Append(err, enc.AppendObject(ps[i])) + } + return err +} diff --git a/vendor/go.uber.org/zap/sugar_test.go b/vendor/go.uber.org/zap/sugar_test.go new file mode 100644 index 0000000000..9e914ecf8a --- /dev/null +++ b/vendor/go.uber.org/zap/sugar_test.go @@ -0,0 +1,515 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "testing" + + "go.uber.org/zap/internal/exit" + "go.uber.org/zap/internal/ztest" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSugarWith(t *testing.T) { + // Convenience functions to create expected error logs. + ignored := func(msg interface{}) observer.LoggedEntry { + return observer.LoggedEntry{ + Entry: zapcore.Entry{Level: ErrorLevel, Message: _oddNumberErrMsg}, + Context: []Field{Any("ignored", msg)}, + } + } + nonString := func(pairs ...invalidPair) observer.LoggedEntry { + return observer.LoggedEntry{ + Entry: zapcore.Entry{Level: ErrorLevel, Message: _nonStringKeyErrMsg}, + Context: []Field{Array("invalid", invalidPairs(pairs))}, + } + } + ignoredError := func(err error) observer.LoggedEntry { + return observer.LoggedEntry{ + Entry: zapcore.Entry{Level: ErrorLevel, Message: _multipleErrMsg}, + Context: []Field{Error(err)}, + } + } + + tests := []struct { + desc string + args []interface{} + expected []Field + errLogs []observer.LoggedEntry + }{ + { + desc: "nil args", + args: nil, + expected: []Field{}, + errLogs: nil, + }, + { + desc: "empty slice of args", + args: []interface{}{}, + expected: []Field{}, + errLogs: nil, + }, + { + desc: "just a dangling key", + args: []interface{}{"should ignore"}, + expected: []Field{}, + errLogs: []observer.LoggedEntry{ignored("should ignore")}, + }, + { + desc: "well-formed key-value pairs", + args: []interface{}{"foo", 42, "true", "bar"}, + expected: []Field{Int("foo", 42), String("true", "bar")}, + errLogs: nil, + }, + { + desc: "just a structured field", + args: []interface{}{Int("foo", 42)}, + expected: []Field{Int("foo", 42)}, + errLogs: nil, + }, + { + desc: "structured field and a dangling key", + args: []interface{}{Int("foo", 42), "dangling"}, + expected: []Field{Int("foo", 42)}, + errLogs: []observer.LoggedEntry{ignored("dangling")}, + }, + { + desc: "structured field and a dangling non-string key", + args: []interface{}{Int("foo", 42), 13}, + expected: []Field{Int("foo", 42)}, + errLogs: []observer.LoggedEntry{ignored(13)}, + }, + { + desc: "key-value pair and a dangling key", + args: []interface{}{"foo", 42, "dangling"}, + expected: []Field{Int("foo", 42)}, + errLogs: []observer.LoggedEntry{ignored("dangling")}, + }, + { + desc: "pairs, a structured field, and a dangling key", + args: []interface{}{"first", "field", Int("foo", 42), "baz", "quux", "dangling"}, + expected: []Field{String("first", "field"), Int("foo", 42), String("baz", "quux")}, + errLogs: []observer.LoggedEntry{ignored("dangling")}, + }, + { + desc: "one non-string key", + args: []interface{}{"foo", 42, true, "bar"}, + expected: []Field{Int("foo", 42)}, + errLogs: []observer.LoggedEntry{nonString(invalidPair{2, true, "bar"})}, + }, + { + desc: "pairs, structured fields, non-string keys, and a dangling key", + args: []interface{}{"foo", 42, true, "bar", Int("structure", 11), 42, "reversed", "baz", "quux", "dangling"}, + expected: []Field{Int("foo", 42), Int("structure", 11), String("baz", "quux")}, + errLogs: []observer.LoggedEntry{ + ignored("dangling"), + nonString(invalidPair{2, true, "bar"}, invalidPair{5, 42, "reversed"}), + }, + }, + { + desc: "multiple errors", + args: []interface{}{errors.New("first"), errors.New("second"), errors.New("third")}, + expected: []Field{Error(errors.New("first"))}, + errLogs: []observer.LoggedEntry{ + ignoredError(errors.New("second")), + ignoredError(errors.New("third")), + }, + }, + } + + for _, tt := range tests { + withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger.With(tt.args...).Info("") + output := logs.AllUntimed() + if len(tt.errLogs) > 0 { + for i := range tt.errLogs { + assert.Equal(t, tt.errLogs[i], output[i], "Unexpected error log at position %d for scenario %s.", i, tt.desc) + } + } + assert.Equal(t, len(tt.errLogs)+1, len(output), "Expected only one non-error message to be logged in scenario %s.", tt.desc) + assert.Equal(t, tt.expected, output[len(tt.errLogs)].Context, "Unexpected message context in scenario %s.", tt.desc) + }) + } +} + +func TestSugaredLoggerLevel(t *testing.T) { + levels := []zapcore.Level{ + DebugLevel, + InfoLevel, + WarnLevel, + ErrorLevel, + DPanicLevel, + PanicLevel, + FatalLevel, + } + + for _, lvl := range levels { + lvl := lvl + t.Run(lvl.String(), func(t *testing.T) { + t.Parallel() + + core, _ := observer.New(lvl) + log := New(core).Sugar() + assert.Equal(t, lvl, log.Level()) + }) + } + + t.Run("Nop", func(t *testing.T) { + t.Parallel() + + assert.Equal(t, zapcore.InvalidLevel, NewNop().Sugar().Level()) + }) +} + +func TestSugarFieldsInvalidPairs(t *testing.T) { + withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger.With(42, "foo", []string{"bar"}, "baz").Info("") + output := logs.AllUntimed() + + // Double-check that the actual message was logged. + require.Equal(t, 2, len(output), "Unexpected number of entries logged.") + require.Equal(t, observer.LoggedEntry{Context: []Field{}}, output[1], "Unexpected non-error log entry.") + + // Assert that the error message's structured fields serialize properly. + require.Equal(t, 1, len(output[0].Context), "Expected one field in error entry context.") + enc := zapcore.NewMapObjectEncoder() + output[0].Context[0].AddTo(enc) + assert.Equal(t, []interface{}{ + map[string]interface{}{"position": int64(0), "key": int64(42), "value": "foo"}, + map[string]interface{}{"position": int64(2), "key": []interface{}{"bar"}, "value": "baz"}, + }, enc.Fields["invalid"], "Unexpected output when logging invalid key-value pairs.") + }) +} + +func TestSugarStructuredLogging(t *testing.T) { + tests := []struct { + msg string + expectMsg string + }{ + {"foo", "foo"}, + {"", ""}, + } + + // Common to all test cases. + var ( + err = errors.New("qux") + context = []interface{}{"foo", "bar"} + extra = []interface{}{err, "baz", false} + expectedFields = []Field{String("foo", "bar"), Error(err), Bool("baz", false)} + ) + + for _, tt := range tests { + withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger.With(context...).Debugw(tt.msg, extra...) + logger.With(context...).Infow(tt.msg, extra...) + logger.With(context...).Warnw(tt.msg, extra...) + logger.With(context...).Errorw(tt.msg, extra...) + logger.With(context...).DPanicw(tt.msg, extra...) + + expected := make([]observer.LoggedEntry, 5) + for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} { + expected[i] = observer.LoggedEntry{ + Entry: zapcore.Entry{Message: tt.expectMsg, Level: lvl}, + Context: expectedFields, + } + } + assert.Equal(t, expected, logs.AllUntimed(), "Unexpected log output.") + }) + } +} + +func TestSugarConcatenatingLogging(t *testing.T) { + tests := []struct { + args []interface{} + expect string + }{ + {[]interface{}{nil}, "<nil>"}, + } + + // Common to all test cases. + context := []interface{}{"foo", "bar"} + expectedFields := []Field{String("foo", "bar")} + + for _, tt := range tests { + withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger.With(context...).Debug(tt.args...) + logger.With(context...).Info(tt.args...) + logger.With(context...).Warn(tt.args...) + logger.With(context...).Error(tt.args...) + logger.With(context...).DPanic(tt.args...) + + expected := make([]observer.LoggedEntry, 5) + for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} { + expected[i] = observer.LoggedEntry{ + Entry: zapcore.Entry{Message: tt.expect, Level: lvl}, + Context: expectedFields, + } + } + assert.Equal(t, expected, logs.AllUntimed(), "Unexpected log output.") + }) + } +} + +func TestSugarTemplatedLogging(t *testing.T) { + tests := []struct { + format string + args []interface{} + expect string + }{ + {"", nil, ""}, + {"foo", nil, "foo"}, + // If the user fails to pass a template, degrade to fmt.Sprint. + {"", []interface{}{"foo"}, "foo"}, + } + + // Common to all test cases. + context := []interface{}{"foo", "bar"} + expectedFields := []Field{String("foo", "bar")} + + for _, tt := range tests { + withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger.With(context...).Debugf(tt.format, tt.args...) + logger.With(context...).Infof(tt.format, tt.args...) + logger.With(context...).Warnf(tt.format, tt.args...) + logger.With(context...).Errorf(tt.format, tt.args...) + logger.With(context...).DPanicf(tt.format, tt.args...) + + expected := make([]observer.LoggedEntry, 5) + for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} { + expected[i] = observer.LoggedEntry{ + Entry: zapcore.Entry{Message: tt.expect, Level: lvl}, + Context: expectedFields, + } + } + assert.Equal(t, expected, logs.AllUntimed(), "Unexpected log output.") + }) + } +} + +func TestSugarLnLogging(t *testing.T) { + tests := []struct { + args []interface{} + expect string + }{ + {nil, ""}, + {[]interface{}{}, ""}, + {[]interface{}{""}, ""}, + {[]interface{}{"foo"}, "foo"}, + {[]interface{}{"foo", "bar"}, "foo bar"}, + } + + // Common to all test cases. + context := []interface{}{"foo", "bar"} + expectedFields := []Field{String("foo", "bar")} + + for _, tt := range tests { + withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger.With(context...).Debugln(tt.args...) + logger.With(context...).Infoln(tt.args...) + logger.With(context...).Warnln(tt.args...) + logger.With(context...).Errorln(tt.args...) + logger.With(context...).DPanicln(tt.args...) + + expected := make([]observer.LoggedEntry, 5) + for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} { + expected[i] = observer.LoggedEntry{ + Entry: zapcore.Entry{Message: tt.expect, Level: lvl}, + Context: expectedFields, + } + } + assert.Equal(t, expected, logs.AllUntimed(), "Unexpected log output.") + }) + } +} + +func TestSugarLnLoggingIgnored(t *testing.T) { + withSugar(t, WarnLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger.Infoln("hello") + assert.Zero(t, logs.Len(), "Expected zero log statements.") + }) +} + +func TestSugarPanicLogging(t *testing.T) { + tests := []struct { + loggerLevel zapcore.Level + f func(*SugaredLogger) + expectedMsg string + }{ + {FatalLevel, func(s *SugaredLogger) { s.Panic("foo") }, ""}, + {PanicLevel, func(s *SugaredLogger) { s.Panic("foo") }, "foo"}, + {DebugLevel, func(s *SugaredLogger) { s.Panic("foo") }, "foo"}, + {FatalLevel, func(s *SugaredLogger) { s.Panicf("%s", "foo") }, ""}, + {PanicLevel, func(s *SugaredLogger) { s.Panicf("%s", "foo") }, "foo"}, + {DebugLevel, func(s *SugaredLogger) { s.Panicf("%s", "foo") }, "foo"}, + {FatalLevel, func(s *SugaredLogger) { s.Panicw("foo") }, ""}, + {PanicLevel, func(s *SugaredLogger) { s.Panicw("foo") }, "foo"}, + {DebugLevel, func(s *SugaredLogger) { s.Panicw("foo") }, "foo"}, + {FatalLevel, func(s *SugaredLogger) { s.Panicln("foo") }, ""}, + {PanicLevel, func(s *SugaredLogger) { s.Panicln("foo") }, "foo"}, + {DebugLevel, func(s *SugaredLogger) { s.Panicln("foo") }, "foo"}, + } + + for _, tt := range tests { + withSugar(t, tt.loggerLevel, nil, func(sugar *SugaredLogger, logs *observer.ObservedLogs) { + assert.Panics(t, func() { tt.f(sugar) }, "Expected panic-level logger calls to panic.") + if tt.expectedMsg != "" { + assert.Equal(t, []observer.LoggedEntry{{ + Context: []Field{}, + Entry: zapcore.Entry{Message: tt.expectedMsg, Level: PanicLevel}, + }}, logs.AllUntimed(), "Unexpected log output.") + } else { + assert.Equal(t, 0, logs.Len(), "Didn't expect any log output.") + } + }) + } +} + +func TestSugarFatalLogging(t *testing.T) { + tests := []struct { + loggerLevel zapcore.Level + f func(*SugaredLogger) + expectedMsg string + }{ + {FatalLevel + 1, func(s *SugaredLogger) { s.Fatal("foo") }, ""}, + {FatalLevel, func(s *SugaredLogger) { s.Fatal("foo") }, "foo"}, + {DebugLevel, func(s *SugaredLogger) { s.Fatal("foo") }, "foo"}, + {FatalLevel + 1, func(s *SugaredLogger) { s.Fatalf("%s", "foo") }, ""}, + {FatalLevel, func(s *SugaredLogger) { s.Fatalf("%s", "foo") }, "foo"}, + {DebugLevel, func(s *SugaredLogger) { s.Fatalf("%s", "foo") }, "foo"}, + {FatalLevel + 1, func(s *SugaredLogger) { s.Fatalw("foo") }, ""}, + {FatalLevel, func(s *SugaredLogger) { s.Fatalw("foo") }, "foo"}, + {DebugLevel, func(s *SugaredLogger) { s.Fatalw("foo") }, "foo"}, + {FatalLevel + 1, func(s *SugaredLogger) { s.Fatalln("foo") }, ""}, + {FatalLevel, func(s *SugaredLogger) { s.Fatalln("foo") }, "foo"}, + {DebugLevel, func(s *SugaredLogger) { s.Fatalln("foo") }, "foo"}, + } + + for _, tt := range tests { + withSugar(t, tt.loggerLevel, nil, func(sugar *SugaredLogger, logs *observer.ObservedLogs) { + stub := exit.WithStub(func() { tt.f(sugar) }) + assert.True(t, stub.Exited, "Expected all calls to fatal logger methods to exit process.") + if tt.expectedMsg != "" { + assert.Equal(t, []observer.LoggedEntry{{ + Context: []Field{}, + Entry: zapcore.Entry{Message: tt.expectedMsg, Level: FatalLevel}, + }}, logs.AllUntimed(), "Unexpected log output.") + } else { + assert.Equal(t, 0, logs.Len(), "Didn't expect any log output.") + } + }) + } +} + +func TestSugarAddCaller(t *testing.T) { + tests := []struct { + options []Option + pat string + }{ + {opts(AddCaller()), `.+/sugar_test.go:[\d]+$`}, + {opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(-1)), `.+/sugar_test.go:[\d]+$`}, + {opts(AddCaller(), AddCallerSkip(1)), `.+/common_test.go:[\d]+$`}, + {opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(5)), `.+/src/runtime/.*:[\d]+$`}, + } + for _, tt := range tests { + withSugar(t, DebugLevel, tt.options, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger.Info("") + output := logs.AllUntimed() + assert.Equal(t, 1, len(output), "Unexpected number of logs written out.") + assert.Regexp( + t, + tt.pat, + output[0].Caller, + "Expected to find package name and file name in output.", + ) + }) + } +} + +func TestSugarAddCallerFail(t *testing.T) { + errBuf := &ztest.Buffer{} + withSugar(t, DebugLevel, opts(AddCaller(), AddCallerSkip(1e3), ErrorOutput(errBuf)), func(log *SugaredLogger, logs *observer.ObservedLogs) { + log.Info("Failure.") + assert.Regexp( + t, + `Logger.check error: failed to get caller`, + errBuf.String(), + "Didn't find expected failure message.", + ) + assert.Equal( + t, + logs.AllUntimed()[0].Message, + "Failure.", + "Expected original message to survive failures in runtime.Caller.") + }) +} + +func TestSugarWithOptionsIncreaseLevel(t *testing.T) { + withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger = logger.WithOptions(IncreaseLevel(WarnLevel)) + logger.Info("logger.Info") + logger.Warn("logger.Warn") + logger.Error("logger.Error") + require.Equal(t, 2, logs.Len(), "expected only warn + error logs due to IncreaseLevel.") + assert.Equal( + t, + logs.AllUntimed()[0].Message, + "logger.Warn", + "Expected first logged message to be warn level message", + ) + }) +} + +func TestSugarLnWithOptionsIncreaseLevel(t *testing.T) { + withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger = logger.WithOptions(IncreaseLevel(WarnLevel)) + logger.Infoln("logger.Infoln") + logger.Warnln("logger.Warnln") + logger.Errorln("logger.Errorln") + require.Equal(t, 2, logs.Len(), "expected only warn + error logs due to IncreaseLevel.") + assert.Equal( + t, + logs.AllUntimed()[0].Message, + "logger.Warnln", + "Expected first logged message to be warn level message", + ) + }) +} + +func BenchmarkSugarSingleStrArg(b *testing.B) { + withSugar(b, InfoLevel, nil /* opts* */, func(log *SugaredLogger, logs *observer.ObservedLogs) { + for i := 0; i < b.N; i++ { + log.Info("hello world") + } + }) +} + +func BenchmarkLnSugarSingleStrArg(b *testing.B) { + withSugar(b, InfoLevel, nil /* opts* */, func(log *SugaredLogger, logs *observer.ObservedLogs) { + for i := 0; i < b.N; i++ { + log.Infoln("hello world") + } + }) +} diff --git a/vendor/go.uber.org/zap/time.go b/vendor/go.uber.org/zap/time.go new file mode 100644 index 0000000000..c5a1f16225 --- /dev/null +++ b/vendor/go.uber.org/zap/time.go @@ -0,0 +1,27 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import "time" + +func timeToMillis(t time.Time) int64 { + return t.UnixNano() / int64(time.Millisecond) +} diff --git a/vendor/go.uber.org/zap/time_test.go b/vendor/go.uber.org/zap/time_test.go new file mode 100644 index 0000000000..cb993ab1fb --- /dev/null +++ b/vendor/go.uber.org/zap/time_test.go @@ -0,0 +1,42 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestTimeToMillis(t *testing.T) { + tests := []struct { + t time.Time + stamp int64 + }{ + {t: time.Unix(0, 0), stamp: 0}, + {t: time.Unix(1, 0), stamp: 1000}, + {t: time.Unix(1, int64(500*time.Millisecond)), stamp: 1500}, + } + for _, tt := range tests { + assert.Equal(t, tt.stamp, timeToMillis(tt.t), "Unexpected timestamp for time %v.", tt.t) + } +} diff --git a/vendor/go.uber.org/zap/writer.go b/vendor/go.uber.org/zap/writer.go new file mode 100644 index 0000000000..06768c6791 --- /dev/null +++ b/vendor/go.uber.org/zap/writer.go @@ -0,0 +1,98 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "fmt" + "io" + + "go.uber.org/zap/zapcore" + + "go.uber.org/multierr" +) + +// Open is a high-level wrapper that takes a variadic number of URLs, opens or +// creates each of the specified resources, and combines them into a locked +// WriteSyncer. It also returns any error encountered and a function to close +// any opened files. +// +// Passing no URLs returns a no-op WriteSyncer. Zap handles URLs without a +// scheme and URLs with the "file" scheme. Third-party code may register +// factories for other schemes using RegisterSink. +// +// URLs with the "file" scheme must use absolute paths on the local +// filesystem. No user, password, port, fragments, or query parameters are +// allowed, and the hostname must be empty or "localhost". +// +// Since it's common to write logs to the local filesystem, URLs without a +// scheme (e.g., "/var/log/foo.log") are treated as local file paths. Without +// a scheme, the special paths "stdout" and "stderr" are interpreted as +// os.Stdout and os.Stderr. When specified without a scheme, relative file +// paths also work. +func Open(paths ...string) (zapcore.WriteSyncer, func(), error) { + writers, closeAll, err := open(paths) + if err != nil { + return nil, nil, err + } + + writer := CombineWriteSyncers(writers...) + return writer, closeAll, nil +} + +func open(paths []string) ([]zapcore.WriteSyncer, func(), error) { + writers := make([]zapcore.WriteSyncer, 0, len(paths)) + closers := make([]io.Closer, 0, len(paths)) + closeAll := func() { + for _, c := range closers { + _ = c.Close() + } + } + + var openErr error + for _, path := range paths { + sink, err := _sinkRegistry.newSink(path) + if err != nil { + openErr = multierr.Append(openErr, fmt.Errorf("open sink %q: %w", path, err)) + continue + } + writers = append(writers, sink) + closers = append(closers, sink) + } + if openErr != nil { + closeAll() + return nil, nil, openErr + } + + return writers, closeAll, nil +} + +// CombineWriteSyncers is a utility that combines multiple WriteSyncers into a +// single, locked WriteSyncer. If no inputs are supplied, it returns a no-op +// WriteSyncer. +// +// It's provided purely as a convenience; the result is no different from +// using zapcore.NewMultiWriteSyncer and zapcore.Lock individually. +func CombineWriteSyncers(writers ...zapcore.WriteSyncer) zapcore.WriteSyncer { + if len(writers) == 0 { + return zapcore.AddSync(io.Discard) + } + return zapcore.Lock(zapcore.NewMultiWriteSyncer(writers...)) +} diff --git a/vendor/go.uber.org/zap/writer_test.go b/vendor/go.uber.org/zap/writer_test.go new file mode 100644 index 0000000000..20e00b74bc --- /dev/null +++ b/vendor/go.uber.org/zap/writer_test.go @@ -0,0 +1,266 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "io" + "io/fs" + "net/url" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/multierr" + "go.uber.org/zap/zapcore" +) + +func TestOpenNoPaths(t *testing.T) { + ws, cleanup, err := Open() + defer cleanup() + + assert.NoError(t, err, "Expected opening no paths to succeed.") + assert.Equal( + t, + zapcore.AddSync(io.Discard), + ws, + "Expected opening no paths to return a no-op WriteSyncer.", + ) +} + +func TestOpen(t *testing.T) { + tempName := filepath.Join(t.TempDir(), "test.log") + assert.False(t, fileExists(tempName)) + require.True(t, filepath.IsAbs(tempName), "Expected absolute temp file path.") + + tests := []struct { + msg string + paths []string + }{ + { + msg: "stdout", + paths: []string{"stdout"}, + }, + { + msg: "stderr", + paths: []string{"stderr"}, + }, + { + msg: "temp file path only", + paths: []string{tempName}, + }, + { + msg: "temp file file scheme", + paths: []string{"file://" + tempName}, + }, + { + msg: "temp file with file scheme and host localhost", + paths: []string{"file://localhost" + tempName}, + }, + } + + for _, tt := range tests { + t.Run(tt.msg, func(t *testing.T) { + _, cleanup, err := Open(tt.paths...) + if err == nil { + defer cleanup() + } + + assert.NoError(t, err, "Unexpected error opening paths %v.", tt.paths) + }) + } + + assert.True(t, fileExists(tempName)) +} + +func TestOpenPathsNotFound(t *testing.T) { + tempName := filepath.Join(t.TempDir(), "test.log") + + tests := []struct { + msg string + paths []string + wantNotFoundPaths []string + }{ + { + msg: "missing path", + paths: []string{"/foo/bar/baz"}, + wantNotFoundPaths: []string{"/foo/bar/baz"}, + }, + { + msg: "missing file scheme url with host localhost", + paths: []string{"file://localhost/foo/bar/baz"}, + wantNotFoundPaths: []string{"/foo/bar/baz"}, + }, + { + msg: "multiple paths", + paths: []string{"stdout", "/foo/bar/baz", tempName, "file:///baz/quux"}, + wantNotFoundPaths: []string{ + "/foo/bar/baz", + "/baz/quux", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.msg, func(t *testing.T) { + _, cleanup, err := Open(tt.paths...) + if !assert.Error(t, err, "Open must fail.") { + cleanup() + return + } + + errs := multierr.Errors(err) + require.Len(t, errs, len(tt.wantNotFoundPaths)) + for i, err := range errs { + assert.ErrorIs(t, err, fs.ErrNotExist) + assert.ErrorContains(t, err, tt.wantNotFoundPaths[i], "missing path in error") + } + }) + } +} + +func TestOpenRelativePath(t *testing.T) { + const name = "test-relative-path.txt" + + require.False(t, fileExists(name), "Test file already exists.") + s, cleanup, err := Open(name) + require.NoError(t, err, "Open failed.") + defer func() { + err := os.Remove(name) + if !t.Failed() { + // If the test has already failed, we probably didn't create this file. + require.NoError(t, err, "Deleting test file failed.") + } + }() + defer cleanup() + + _, err = s.Write([]byte("test")) + assert.NoError(t, err, "Write failed.") + assert.True(t, fileExists(name), "Didn't create file for relative path.") +} + +func TestOpenFails(t *testing.T) { + tests := []struct { + paths []string + }{ + {paths: []string{"./non-existent-dir/file"}}, // directory doesn't exist + {paths: []string{"stdout", "./non-existent-dir/file"}}, // directory doesn't exist + {paths: []string{"://foo.log"}}, // invalid URL, scheme can't begin with colon + {paths: []string{"mem://somewhere"}}, // scheme not registered + } + + for _, tt := range tests { + _, cleanup, err := Open(tt.paths...) + require.Nil(t, cleanup, "Cleanup function should never be nil") + assert.Error(t, err, "Open with invalid URL should fail.") + } +} + +func TestOpenOtherErrors(t *testing.T) { + tempName := filepath.Join(t.TempDir(), "test.log") + + tests := []struct { + msg string + paths []string + wantErr string + }{ + { + msg: "file with unexpected host", + paths: []string{"file://host01.test.com" + tempName}, + wantErr: "empty or use localhost", + }, + { + msg: "file with user on localhost", + paths: []string{"file://rms@localhost" + tempName}, + wantErr: "user and password not allowed", + }, + { + msg: "file url with fragment", + paths: []string{"file://localhost" + tempName + "#foo"}, + wantErr: "fragments not allowed", + }, + { + msg: "file url with query", + paths: []string{"file://localhost" + tempName + "?foo=bar"}, + wantErr: "query parameters not allowed", + }, + { + msg: "file with port", + paths: []string{"file://localhost:8080" + tempName}, + wantErr: "ports not allowed", + }, + } + + for _, tt := range tests { + t.Run(tt.msg, func(t *testing.T) { + _, cleanup, err := Open(tt.paths...) + if !assert.Error(t, err, "Open must fail.") { + cleanup() + return + } + + assert.ErrorContains(t, err, tt.wantErr, "Unexpected error opening paths %v.", tt.paths) + }) + } +} + +type testWriter struct { + expected string + t testing.TB +} + +func (w *testWriter) Write(actual []byte) (int, error) { + assert.Equal(w.t, []byte(w.expected), actual, "Unexpected write error.") + return len(actual), nil +} + +func (w *testWriter) Sync() error { + return nil +} + +func TestOpenWithErroringSinkFactory(t *testing.T) { + stubSinkRegistry(t) + + msg := "expected factory error" + factory := func(_ *url.URL) (Sink, error) { + return nil, errors.New(msg) + } + + assert.NoError(t, RegisterSink("test", factory), "Failed to register sink factory.") + _, _, err := Open("test://some/path") + assert.ErrorContains(t, err, msg) +} + +func TestCombineWriteSyncers(t *testing.T) { + tw := &testWriter{"test", t} + w := CombineWriteSyncers(tw) + _, err := w.Write([]byte("test")) + assert.NoError(t, err, "Unexpected write error.") +} + +func fileExists(name string) bool { + if _, err := os.Stat(name); os.IsNotExist(err) { + return false + } + return true +} diff --git a/vendor/go.uber.org/zap/ya.make b/vendor/go.uber.org/zap/ya.make new file mode 100644 index 0000000000..7a40d4b2a9 --- /dev/null +++ b/vendor/go.uber.org/zap/ya.make @@ -0,0 +1,66 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + array.go + config.go + doc.go + encoder.go + error.go + field.go + flag.go + global.go + http_handler.go + level.go + logger.go + options.go + sink.go + sugar.go + time.go + writer.go +) + +GO_TEST_SRCS( + array_test.go + clock_test.go + common_test.go + config_test.go + encoder_test.go + error_test.go + field_test.go + flag_test.go + global_test.go + increase_level_test.go + leak_test.go + level_test.go + logger_bench_test.go + logger_test.go + sink_test.go + sugar_test.go + time_test.go + writer_test.go +) + +GO_XTEST_SRCS( + example_test.go + http_handler_test.go + # stacktrace_ext_test.go +) + +IF (OS_WINDOWS) + GO_TEST_SRCS(sink_windows_test.go) +ENDIF() + +END() + +RECURSE( + # benchmarks + buffer + gotest + internal + zapcore + zapgrpc + zapio + zaptest +) diff --git a/vendor/go.uber.org/zap/zapcore/buffered_write_syncer.go b/vendor/go.uber.org/zap/zapcore/buffered_write_syncer.go new file mode 100644 index 0000000000..a40e93b3ec --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/buffered_write_syncer.go @@ -0,0 +1,219 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bufio" + "sync" + "time" + + "go.uber.org/multierr" +) + +const ( + // _defaultBufferSize specifies the default size used by Buffer. + _defaultBufferSize = 256 * 1024 // 256 kB + + // _defaultFlushInterval specifies the default flush interval for + // Buffer. + _defaultFlushInterval = 30 * time.Second +) + +// A BufferedWriteSyncer is a WriteSyncer that buffers writes in-memory before +// flushing them to a wrapped WriteSyncer after reaching some limit, or at some +// fixed interval--whichever comes first. +// +// BufferedWriteSyncer is safe for concurrent use. You don't need to use +// zapcore.Lock for WriteSyncers with BufferedWriteSyncer. +// +// To set up a BufferedWriteSyncer, construct a WriteSyncer for your log +// destination (*os.File is a valid WriteSyncer), wrap it with +// BufferedWriteSyncer, and defer a Stop() call for when you no longer need the +// object. +// +// func main() { +// ws := ... // your log destination +// bws := &zapcore.BufferedWriteSyncer{WS: ws} +// defer bws.Stop() +// +// // ... +// core := zapcore.NewCore(enc, bws, lvl) +// logger := zap.New(core) +// +// // ... +// } +// +// By default, a BufferedWriteSyncer will buffer up to 256 kilobytes of logs, +// waiting at most 30 seconds between flushes. +// You can customize these parameters by setting the Size or FlushInterval +// fields. +// For example, the following buffers up to 512 kB of logs before flushing them +// to Stderr, with a maximum of one minute between each flush. +// +// ws := &BufferedWriteSyncer{ +// WS: os.Stderr, +// Size: 512 * 1024, // 512 kB +// FlushInterval: time.Minute, +// } +// defer ws.Stop() +type BufferedWriteSyncer struct { + // WS is the WriteSyncer around which BufferedWriteSyncer will buffer + // writes. + // + // This field is required. + WS WriteSyncer + + // Size specifies the maximum amount of data the writer will buffered + // before flushing. + // + // Defaults to 256 kB if unspecified. + Size int + + // FlushInterval specifies how often the writer should flush data if + // there have been no writes. + // + // Defaults to 30 seconds if unspecified. + FlushInterval time.Duration + + // Clock, if specified, provides control of the source of time for the + // writer. + // + // Defaults to the system clock. + Clock Clock + + // unexported fields for state + mu sync.Mutex + initialized bool // whether initialize() has run + stopped bool // whether Stop() has run + writer *bufio.Writer + ticker *time.Ticker + stop chan struct{} // closed when flushLoop should stop + done chan struct{} // closed when flushLoop has stopped +} + +func (s *BufferedWriteSyncer) initialize() { + size := s.Size + if size == 0 { + size = _defaultBufferSize + } + + flushInterval := s.FlushInterval + if flushInterval == 0 { + flushInterval = _defaultFlushInterval + } + + if s.Clock == nil { + s.Clock = DefaultClock + } + + s.ticker = s.Clock.NewTicker(flushInterval) + s.writer = bufio.NewWriterSize(s.WS, size) + s.stop = make(chan struct{}) + s.done = make(chan struct{}) + s.initialized = true + go s.flushLoop() +} + +// Write writes log data into buffer syncer directly, multiple Write calls will be batched, +// and log data will be flushed to disk when the buffer is full or periodically. +func (s *BufferedWriteSyncer) Write(bs []byte) (int, error) { + s.mu.Lock() + defer s.mu.Unlock() + + if !s.initialized { + s.initialize() + } + + // To avoid partial writes from being flushed, we manually flush the existing buffer if: + // * The current write doesn't fit into the buffer fully, and + // * The buffer is not empty (since bufio will not split large writes when the buffer is empty) + if len(bs) > s.writer.Available() && s.writer.Buffered() > 0 { + if err := s.writer.Flush(); err != nil { + return 0, err + } + } + + return s.writer.Write(bs) +} + +// Sync flushes buffered log data into disk directly. +func (s *BufferedWriteSyncer) Sync() error { + s.mu.Lock() + defer s.mu.Unlock() + + var err error + if s.initialized { + err = s.writer.Flush() + } + + return multierr.Append(err, s.WS.Sync()) +} + +// flushLoop flushes the buffer at the configured interval until Stop is +// called. +func (s *BufferedWriteSyncer) flushLoop() { + defer close(s.done) + + for { + select { + case <-s.ticker.C: + // we just simply ignore error here + // because the underlying bufio writer stores any errors + // and we return any error from Sync() as part of the close + _ = s.Sync() + case <-s.stop: + return + } + } +} + +// Stop closes the buffer, cleans up background goroutines, and flushes +// remaining unwritten data. +func (s *BufferedWriteSyncer) Stop() (err error) { + var stopped bool + + // Critical section. + func() { + s.mu.Lock() + defer s.mu.Unlock() + + if !s.initialized { + return + } + + stopped = s.stopped + if stopped { + return + } + s.stopped = true + + s.ticker.Stop() + close(s.stop) // tell flushLoop to stop + <-s.done // and wait until it has + }() + + // Don't call Sync on consecutive Stops. + if !stopped { + err = s.Sync() + } + + return err +} diff --git a/vendor/go.uber.org/zap/zapcore/buffered_write_syncer_bench_test.go b/vendor/go.uber.org/zap/zapcore/buffered_write_syncer_bench_test.go new file mode 100644 index 0000000000..56ad5f2c66 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/buffered_write_syncer_bench_test.go @@ -0,0 +1,55 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func BenchmarkBufferedWriteSyncer(b *testing.B) { + b.Run("write file with buffer", func(b *testing.B) { + file, err := os.CreateTemp(b.TempDir(), "test.log") + require.NoError(b, err) + + defer func() { + assert.NoError(b, file.Close()) + }() + + w := &BufferedWriteSyncer{ + WS: AddSync(file), + } + defer func() { + assert.NoError(b, w.Stop(), "failed to stop buffered write syncer") + }() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := w.Write([]byte("foobarbazbabble")); err != nil { + b.Fatal(err) + } + } + }) + }) +} diff --git a/vendor/go.uber.org/zap/zapcore/buffered_write_syncer_test.go b/vendor/go.uber.org/zap/zapcore/buffered_write_syncer_test.go new file mode 100644 index 0000000000..d0f6037af0 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/buffered_write_syncer_test.go @@ -0,0 +1,140 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bytes" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/internal/ztest" +) + +func TestBufferWriter(t *testing.T) { + // If we pass a plain io.Writer, make sure that we still get a WriteSyncer + // with a no-op Sync. + t.Run("sync", func(t *testing.T) { + buf := &bytes.Buffer{} + ws := &BufferedWriteSyncer{WS: AddSync(buf)} + + requireWriteWorks(t, ws) + assert.Empty(t, buf.String(), "Unexpected log calling a no-op Write method.") + assert.NoError(t, ws.Sync(), "Unexpected error calling a no-op Sync method.") + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + assert.NoError(t, ws.Stop()) + }) + + t.Run("stop", func(t *testing.T) { + buf := &bytes.Buffer{} + ws := &BufferedWriteSyncer{WS: AddSync(buf)} + requireWriteWorks(t, ws) + assert.Empty(t, buf.String(), "Unexpected log calling a no-op Write method.") + assert.NoError(t, ws.Stop()) + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + }) + + t.Run("stop twice", func(t *testing.T) { + ws := &BufferedWriteSyncer{WS: &ztest.FailWriter{}} + _, err := ws.Write([]byte("foo")) + require.NoError(t, err, "Unexpected error writing to WriteSyncer.") + assert.Error(t, ws.Stop(), "Expected stop to fail.") + assert.NoError(t, ws.Stop(), "Expected stop to not fail.") + }) + + t.Run("wrap twice", func(t *testing.T) { + buf := &bytes.Buffer{} + bufsync := &BufferedWriteSyncer{WS: AddSync(buf)} + ws := &BufferedWriteSyncer{WS: bufsync} + requireWriteWorks(t, ws) + assert.Empty(t, buf.String(), "Unexpected log calling a no-op Write method.") + require.NoError(t, ws.Sync()) + assert.Equal(t, "foo", buf.String()) + assert.NoError(t, ws.Stop()) + assert.NoError(t, bufsync.Stop()) + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + }) + + t.Run("small buffer", func(t *testing.T) { + buf := &bytes.Buffer{} + ws := &BufferedWriteSyncer{WS: AddSync(buf), Size: 5} + + requireWriteWorks(t, ws) + assert.Equal(t, "", buf.String(), "Unexpected log calling a no-op Write method.") + requireWriteWorks(t, ws) + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + assert.NoError(t, ws.Stop()) + }) + + t.Run("with lockedWriteSyncer", func(t *testing.T) { + buf := &bytes.Buffer{} + ws := &BufferedWriteSyncer{WS: Lock(AddSync(buf)), Size: 5} + + requireWriteWorks(t, ws) + assert.Equal(t, "", buf.String(), "Unexpected log calling a no-op Write method.") + requireWriteWorks(t, ws) + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + assert.NoError(t, ws.Stop()) + }) + + t.Run("flush error", func(t *testing.T) { + ws := &BufferedWriteSyncer{WS: &ztest.FailWriter{}, Size: 4} + n, err := ws.Write([]byte("foo")) + require.NoError(t, err, "Unexpected error writing to WriteSyncer.") + require.Equal(t, 3, n, "Wrote an unexpected number of bytes.") + _, err = ws.Write([]byte("foo")) + assert.Error(t, err, "Expected error writing to WriteSyncer.") + assert.Error(t, ws.Stop(), "Expected stop to fail.") + }) + + t.Run("flush timer", func(t *testing.T) { + buf := &bytes.Buffer{} + clock := ztest.NewMockClock() + ws := &BufferedWriteSyncer{ + WS: AddSync(buf), + Size: 6, + FlushInterval: time.Microsecond, + Clock: clock, + } + requireWriteWorks(t, ws) + clock.Add(10 * time.Microsecond) + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + + // flush twice to validate loop logic + requireWriteWorks(t, ws) + clock.Add(10 * time.Microsecond) + assert.Equal(t, "foofoo", buf.String(), "Unexpected log string") + assert.NoError(t, ws.Stop()) + }) +} + +func TestBufferWriterWithoutStart(t *testing.T) { + t.Run("stop", func(t *testing.T) { + ws := &BufferedWriteSyncer{WS: AddSync(new(bytes.Buffer))} + assert.NoError(t, ws.Stop(), "Stop must not fail") + }) + + t.Run("Sync", func(t *testing.T) { + ws := &BufferedWriteSyncer{WS: AddSync(new(bytes.Buffer))} + assert.NoError(t, ws.Sync(), "Sync must not fail") + }) +} diff --git a/vendor/go.uber.org/zap/zapcore/clock.go b/vendor/go.uber.org/zap/zapcore/clock.go new file mode 100644 index 0000000000..422fd82a6b --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/clock.go @@ -0,0 +1,48 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "time" + +// DefaultClock is the default clock used by Zap in operations that require +// time. This clock uses the system clock for all operations. +var DefaultClock = systemClock{} + +// Clock is a source of time for logged entries. +type Clock interface { + // Now returns the current local time. + Now() time.Time + + // NewTicker returns *time.Ticker that holds a channel + // that delivers "ticks" of a clock. + NewTicker(time.Duration) *time.Ticker +} + +// systemClock implements default Clock that uses system time. +type systemClock struct{} + +func (systemClock) Now() time.Time { + return time.Now() +} + +func (systemClock) NewTicker(duration time.Duration) *time.Ticker { + return time.NewTicker(duration) +} diff --git a/vendor/go.uber.org/zap/zapcore/clock_test.go b/vendor/go.uber.org/zap/zapcore/clock_test.go new file mode 100644 index 0000000000..0dff349914 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/clock_test.go @@ -0,0 +1,44 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "testing" + "time" + + "go.uber.org/zap/internal/ztest" +) + +// Verify that the mock clock satisfies the Clock interface. +var _ Clock = (*ztest.MockClock)(nil) + +func TestSystemClock_NewTicker(t *testing.T) { + want := 3 + + var n int + timer := DefaultClock.NewTicker(time.Millisecond) + for range timer.C { + n++ + if n == want { + return + } + } +} diff --git a/vendor/go.uber.org/zap/zapcore/console_encoder.go b/vendor/go.uber.org/zap/zapcore/console_encoder.go new file mode 100644 index 0000000000..8ca0bfaf56 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/console_encoder.go @@ -0,0 +1,157 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "fmt" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/internal/bufferpool" + "go.uber.org/zap/internal/pool" +) + +var _sliceEncoderPool = pool.New(func() *sliceArrayEncoder { + return &sliceArrayEncoder{ + elems: make([]interface{}, 0, 2), + } +}) + +func getSliceEncoder() *sliceArrayEncoder { + return _sliceEncoderPool.Get() +} + +func putSliceEncoder(e *sliceArrayEncoder) { + e.elems = e.elems[:0] + _sliceEncoderPool.Put(e) +} + +type consoleEncoder struct { + *jsonEncoder +} + +// NewConsoleEncoder creates an encoder whose output is designed for human - +// rather than machine - consumption. It serializes the core log entry data +// (message, level, timestamp, etc.) in a plain-text format and leaves the +// structured context as JSON. +// +// Note that although the console encoder doesn't use the keys specified in the +// encoder configuration, it will omit any element whose key is set to the empty +// string. +func NewConsoleEncoder(cfg EncoderConfig) Encoder { + if cfg.ConsoleSeparator == "" { + // Use a default delimiter of '\t' for backwards compatibility + cfg.ConsoleSeparator = "\t" + } + return consoleEncoder{newJSONEncoder(cfg, true)} +} + +func (c consoleEncoder) Clone() Encoder { + return consoleEncoder{c.jsonEncoder.Clone().(*jsonEncoder)} +} + +func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, error) { + line := bufferpool.Get() + + // We don't want the entry's metadata to be quoted and escaped (if it's + // encoded as strings), which means that we can't use the JSON encoder. The + // simplest option is to use the memory encoder and fmt.Fprint. + // + // If this ever becomes a performance bottleneck, we can implement + // ArrayEncoder for our plain-text format. + arr := getSliceEncoder() + if c.TimeKey != "" && c.EncodeTime != nil { + c.EncodeTime(ent.Time, arr) + } + if c.LevelKey != "" && c.EncodeLevel != nil { + c.EncodeLevel(ent.Level, arr) + } + if ent.LoggerName != "" && c.NameKey != "" { + nameEncoder := c.EncodeName + + if nameEncoder == nil { + // Fall back to FullNameEncoder for backward compatibility. + nameEncoder = FullNameEncoder + } + + nameEncoder(ent.LoggerName, arr) + } + if ent.Caller.Defined { + if c.CallerKey != "" && c.EncodeCaller != nil { + c.EncodeCaller(ent.Caller, arr) + } + if c.FunctionKey != "" { + arr.AppendString(ent.Caller.Function) + } + } + for i := range arr.elems { + if i > 0 { + line.AppendString(c.ConsoleSeparator) + } + fmt.Fprint(line, arr.elems[i]) + } + putSliceEncoder(arr) + + // Add the message itself. + if c.MessageKey != "" { + c.addSeparatorIfNecessary(line) + line.AppendString(ent.Message) + } + + // Add any structured context. + c.writeContext(line, fields) + + // If there's no stacktrace key, honor that; this allows users to force + // single-line output. + if ent.Stack != "" && c.StacktraceKey != "" { + line.AppendByte('\n') + line.AppendString(ent.Stack) + } + + line.AppendString(c.LineEnding) + return line, nil +} + +func (c consoleEncoder) writeContext(line *buffer.Buffer, extra []Field) { + context := c.jsonEncoder.Clone().(*jsonEncoder) + defer func() { + // putJSONEncoder assumes the buffer is still used, but we write out the buffer so + // we can free it. + context.buf.Free() + putJSONEncoder(context) + }() + + addFields(context, extra) + context.closeOpenNamespaces() + if context.buf.Len() == 0 { + return + } + + c.addSeparatorIfNecessary(line) + line.AppendByte('{') + line.Write(context.buf.Bytes()) + line.AppendByte('}') +} + +func (c consoleEncoder) addSeparatorIfNecessary(line *buffer.Buffer) { + if line.Len() > 0 { + line.AppendString(c.ConsoleSeparator) + } +} diff --git a/vendor/go.uber.org/zap/zapcore/console_encoder_bench_test.go b/vendor/go.uber.org/zap/zapcore/console_encoder_bench_test.go new file mode 100644 index 0000000000..62feaea717 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/console_encoder_bench_test.go @@ -0,0 +1,49 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "testing" + + . "go.uber.org/zap/zapcore" +) + +func BenchmarkZapConsole(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + enc := NewConsoleEncoder(humanEncoderConfig()) + enc.AddString("str", "foo") + enc.AddInt64("int64-1", 1) + enc.AddInt64("int64-2", 2) + enc.AddFloat64("float64", 1.0) + enc.AddString("string1", "\n") + enc.AddString("string2", "💩") + enc.AddString("string3", "🤔") + enc.AddString("string4", "🙊") + enc.AddBool("bool", true) + buf, _ := enc.EncodeEntry(Entry{ + Message: "fake", + Level: DebugLevel, + }, nil) + buf.Free() + } + }) +} diff --git a/vendor/go.uber.org/zap/zapcore/console_encoder_test.go b/vendor/go.uber.org/zap/zapcore/console_encoder_test.go new file mode 100644 index 0000000000..b03f1a728a --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/console_encoder_test.go @@ -0,0 +1,91 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +package zapcore_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + . "go.uber.org/zap/zapcore" +) + +var ( + testEntry = Entry{ + LoggerName: "main", + Level: InfoLevel, + Message: `hello`, + Time: _epoch, + Stack: "fake-stack", + Caller: EntryCaller{Defined: true, File: "foo.go", Line: 42, Function: "foo.Foo"}, + } +) + +func TestConsoleSeparator(t *testing.T) { + tests := []struct { + desc string + separator string + wantConsole string + }{ + { + desc: "space console separator", + separator: " ", + wantConsole: "0 info main foo.go:42 foo.Foo hello\nfake-stack\n", + }, + { + desc: "default console separator", + separator: "", + wantConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "tag console separator", + separator: "\t", + wantConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "dash console separator", + separator: "--", + wantConsole: "0--info--main--foo.go:42--foo.Foo--hello\nfake-stack\n", + }, + } + + for _, tt := range tests { + console := NewConsoleEncoder(encoderTestEncoderConfig(tt.separator)) + t.Run(tt.desc, func(t *testing.T) { + entry := testEntry + consoleOut, err := console.EncodeEntry(entry, nil) + if !assert.NoError(t, err) { + return + } + assert.Equal( + t, + tt.wantConsole, + consoleOut.String(), + "Unexpected console output", + ) + }) + + } +} + +func encoderTestEncoderConfig(separator string) EncoderConfig { + testEncoder := testEncoderConfig() + testEncoder.ConsoleSeparator = separator + return testEncoder +} diff --git a/vendor/go.uber.org/zap/zapcore/core.go b/vendor/go.uber.org/zap/zapcore/core.go new file mode 100644 index 0000000000..776e93f6f3 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/core.go @@ -0,0 +1,122 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +// Core is a minimal, fast logger interface. It's designed for library authors +// to wrap in a more user-friendly API. +type Core interface { + LevelEnabler + + // With adds structured context to the Core. + With([]Field) Core + // Check determines whether the supplied Entry should be logged (using the + // embedded LevelEnabler and possibly some extra logic). If the entry + // should be logged, the Core adds itself to the CheckedEntry and returns + // the result. + // + // Callers must use Check before calling Write. + Check(Entry, *CheckedEntry) *CheckedEntry + // Write serializes the Entry and any Fields supplied at the log site and + // writes them to their destination. + // + // If called, Write should always log the Entry and Fields; it should not + // replicate the logic of Check. + Write(Entry, []Field) error + // Sync flushes buffered logs (if any). + Sync() error +} + +type nopCore struct{} + +// NewNopCore returns a no-op Core. +func NewNopCore() Core { return nopCore{} } +func (nopCore) Enabled(Level) bool { return false } +func (n nopCore) With([]Field) Core { return n } +func (nopCore) Check(_ Entry, ce *CheckedEntry) *CheckedEntry { return ce } +func (nopCore) Write(Entry, []Field) error { return nil } +func (nopCore) Sync() error { return nil } + +// NewCore creates a Core that writes logs to a WriteSyncer. +func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core { + return &ioCore{ + LevelEnabler: enab, + enc: enc, + out: ws, + } +} + +type ioCore struct { + LevelEnabler + enc Encoder + out WriteSyncer +} + +var ( + _ Core = (*ioCore)(nil) + _ leveledEnabler = (*ioCore)(nil) +) + +func (c *ioCore) Level() Level { + return LevelOf(c.LevelEnabler) +} + +func (c *ioCore) With(fields []Field) Core { + clone := c.clone() + addFields(clone.enc, fields) + return clone +} + +func (c *ioCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + if c.Enabled(ent.Level) { + return ce.AddCore(ent, c) + } + return ce +} + +func (c *ioCore) Write(ent Entry, fields []Field) error { + buf, err := c.enc.EncodeEntry(ent, fields) + if err != nil { + return err + } + _, err = c.out.Write(buf.Bytes()) + buf.Free() + if err != nil { + return err + } + if ent.Level > ErrorLevel { + // Since we may be crashing the program, sync the output. + // Ignore Sync errors, pending a clean solution to issue #370. + _ = c.Sync() + } + return nil +} + +func (c *ioCore) Sync() error { + return c.out.Sync() +} + +func (c *ioCore) clone() *ioCore { + return &ioCore{ + LevelEnabler: c.LevelEnabler, + enc: c.enc.Clone(), + out: c.out, + } +} diff --git a/vendor/go.uber.org/zap/zapcore/core_test.go b/vendor/go.uber.org/zap/zapcore/core_test.go new file mode 100644 index 0000000000..e3186311ac --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/core_test.go @@ -0,0 +1,165 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "errors" + "os" + "testing" + "time" + + "go.uber.org/zap/internal/ztest" + . "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func makeInt64Field(key string, val int) Field { + return Field{Type: Int64Type, Integer: int64(val), Key: key} +} + +func TestNopCore(t *testing.T) { + entry := Entry{ + Message: "test", + Level: InfoLevel, + Time: time.Now(), + LoggerName: "main", + Stack: "fake-stack", + } + ce := &CheckedEntry{} + + allLevels := []Level{ + DebugLevel, + InfoLevel, + WarnLevel, + ErrorLevel, + DPanicLevel, + PanicLevel, + FatalLevel, + } + core := NewNopCore() + assert.Equal(t, core, core.With([]Field{makeInt64Field("k", 42)}), "Expected no-op With.") + for _, level := range allLevels { + assert.False(t, core.Enabled(level), "Expected all levels to be disabled in no-op core.") + assert.Equal(t, ce, core.Check(entry, ce), "Expected no-op Check to return checked entry unchanged.") + assert.NoError(t, core.Write(entry, nil), "Expected no-op Writes to always succeed.") + assert.NoError(t, core.Sync(), "Expected no-op Syncs to always succeed.") + } +} + +func TestIOCore(t *testing.T) { + temp, err := os.CreateTemp(t.TempDir(), "test.log") + require.NoError(t, err) + + // Drop timestamps for simpler assertions (timestamp encoding is tested + // elsewhere). + cfg := testEncoderConfig() + cfg.TimeKey = "" + + core := NewCore( + NewJSONEncoder(cfg), + temp, + InfoLevel, + ).With([]Field{makeInt64Field("k", 1)}) + defer assert.NoError(t, core.Sync(), "Expected Syncing a temp file to succeed.") + + t.Run("LevelOf", func(t *testing.T) { + assert.Equal(t, InfoLevel, LevelOf(core), "Incorrect Core Level") + }) + + if ce := core.Check(Entry{Level: DebugLevel, Message: "debug"}, nil); ce != nil { + ce.Write(makeInt64Field("k", 2)) + } + if ce := core.Check(Entry{Level: InfoLevel, Message: "info"}, nil); ce != nil { + ce.Write(makeInt64Field("k", 3)) + } + if ce := core.Check(Entry{Level: WarnLevel, Message: "warn"}, nil); ce != nil { + ce.Write(makeInt64Field("k", 4)) + } + + logged, err := os.ReadFile(temp.Name()) + require.NoError(t, err, "Failed to read from temp file.") + require.Equal( + t, + `{"level":"info","msg":"info","k":1,"k":3}`+"\n"+ + `{"level":"warn","msg":"warn","k":1,"k":4}`+"\n", + string(logged), + "Unexpected log output.", + ) +} + +func TestIOCoreSyncFail(t *testing.T) { + sink := &ztest.Discarder{} + err := errors.New("failed") + sink.SetError(err) + + core := NewCore( + NewJSONEncoder(testEncoderConfig()), + sink, + DebugLevel, + ) + + assert.Equal( + t, + err, + core.Sync(), + "Expected core.Sync to return errors from underlying WriteSyncer.", + ) +} + +func TestIOCoreSyncsOutput(t *testing.T) { + tests := []struct { + entry Entry + shouldSync bool + }{ + {Entry{Level: DebugLevel}, false}, + {Entry{Level: InfoLevel}, false}, + {Entry{Level: WarnLevel}, false}, + {Entry{Level: ErrorLevel}, false}, + {Entry{Level: DPanicLevel}, true}, + {Entry{Level: PanicLevel}, true}, + {Entry{Level: FatalLevel}, true}, + } + + for _, tt := range tests { + sink := &ztest.Discarder{} + core := NewCore( + NewJSONEncoder(testEncoderConfig()), + sink, + DebugLevel, + ) + + assert.NoError(t, core.Write(tt.entry, nil), "Unexpected error writing entry.") + assert.Equal(t, tt.shouldSync, sink.Called(), "Incorrect Sync behavior.") + } +} + +func TestIOCoreWriteFailure(t *testing.T) { + core := NewCore( + NewJSONEncoder(testEncoderConfig()), + Lock(&ztest.FailWriter{}), + DebugLevel, + ) + err := core.Write(Entry{}, nil) + // Should log the error. + assert.Error(t, err, "Expected writing Entry to fail.") +} diff --git a/vendor/go.uber.org/zap/zapcore/doc.go b/vendor/go.uber.org/zap/zapcore/doc.go new file mode 100644 index 0000000000..31000e91f7 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/doc.go @@ -0,0 +1,24 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package zapcore defines and implements the low-level interfaces upon which +// zap is built. By providing alternate implementations of these interfaces, +// external packages can extend zap's capabilities. +package zapcore // import "go.uber.org/zap/zapcore" diff --git a/vendor/go.uber.org/zap/zapcore/encoder.go b/vendor/go.uber.org/zap/zapcore/encoder.go new file mode 100644 index 0000000000..5769ff3e4e --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/encoder.go @@ -0,0 +1,451 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "encoding/json" + "io" + "time" + + "go.uber.org/zap/buffer" +) + +// DefaultLineEnding defines the default line ending when writing logs. +// Alternate line endings specified in EncoderConfig can override this +// behavior. +const DefaultLineEnding = "\n" + +// OmitKey defines the key to use when callers want to remove a key from log output. +const OmitKey = "" + +// A LevelEncoder serializes a Level to a primitive type. +type LevelEncoder func(Level, PrimitiveArrayEncoder) + +// LowercaseLevelEncoder serializes a Level to a lowercase string. For example, +// InfoLevel is serialized to "info". +func LowercaseLevelEncoder(l Level, enc PrimitiveArrayEncoder) { + enc.AppendString(l.String()) +} + +// LowercaseColorLevelEncoder serializes a Level to a lowercase string and adds coloring. +// For example, InfoLevel is serialized to "info" and colored blue. +func LowercaseColorLevelEncoder(l Level, enc PrimitiveArrayEncoder) { + s, ok := _levelToLowercaseColorString[l] + if !ok { + s = _unknownLevelColor.Add(l.String()) + } + enc.AppendString(s) +} + +// CapitalLevelEncoder serializes a Level to an all-caps string. For example, +// InfoLevel is serialized to "INFO". +func CapitalLevelEncoder(l Level, enc PrimitiveArrayEncoder) { + enc.AppendString(l.CapitalString()) +} + +// CapitalColorLevelEncoder serializes a Level to an all-caps string and adds color. +// For example, InfoLevel is serialized to "INFO" and colored blue. +func CapitalColorLevelEncoder(l Level, enc PrimitiveArrayEncoder) { + s, ok := _levelToCapitalColorString[l] + if !ok { + s = _unknownLevelColor.Add(l.CapitalString()) + } + enc.AppendString(s) +} + +// UnmarshalText unmarshals text to a LevelEncoder. "capital" is unmarshaled to +// CapitalLevelEncoder, "coloredCapital" is unmarshaled to CapitalColorLevelEncoder, +// "colored" is unmarshaled to LowercaseColorLevelEncoder, and anything else +// is unmarshaled to LowercaseLevelEncoder. +func (e *LevelEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "capital": + *e = CapitalLevelEncoder + case "capitalColor": + *e = CapitalColorLevelEncoder + case "color": + *e = LowercaseColorLevelEncoder + default: + *e = LowercaseLevelEncoder + } + return nil +} + +// A TimeEncoder serializes a time.Time to a primitive type. +type TimeEncoder func(time.Time, PrimitiveArrayEncoder) + +// EpochTimeEncoder serializes a time.Time to a floating-point number of seconds +// since the Unix epoch. +func EpochTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + nanos := t.UnixNano() + sec := float64(nanos) / float64(time.Second) + enc.AppendFloat64(sec) +} + +// EpochMillisTimeEncoder serializes a time.Time to a floating-point number of +// milliseconds since the Unix epoch. +func EpochMillisTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + nanos := t.UnixNano() + millis := float64(nanos) / float64(time.Millisecond) + enc.AppendFloat64(millis) +} + +// EpochNanosTimeEncoder serializes a time.Time to an integer number of +// nanoseconds since the Unix epoch. +func EpochNanosTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + enc.AppendInt64(t.UnixNano()) +} + +func encodeTimeLayout(t time.Time, layout string, enc PrimitiveArrayEncoder) { + type appendTimeEncoder interface { + AppendTimeLayout(time.Time, string) + } + + if enc, ok := enc.(appendTimeEncoder); ok { + enc.AppendTimeLayout(t, layout) + return + } + + enc.AppendString(t.Format(layout)) +} + +// ISO8601TimeEncoder serializes a time.Time to an ISO8601-formatted string +// with millisecond precision. +// +// If enc supports AppendTimeLayout(t time.Time,layout string), it's used +// instead of appending a pre-formatted string value. +func ISO8601TimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + encodeTimeLayout(t, "2006-01-02T15:04:05.000Z0700", enc) +} + +// RFC3339TimeEncoder serializes a time.Time to an RFC3339-formatted string. +// +// If enc supports AppendTimeLayout(t time.Time,layout string), it's used +// instead of appending a pre-formatted string value. +func RFC3339TimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + encodeTimeLayout(t, time.RFC3339, enc) +} + +// RFC3339NanoTimeEncoder serializes a time.Time to an RFC3339-formatted string +// with nanosecond precision. +// +// If enc supports AppendTimeLayout(t time.Time,layout string), it's used +// instead of appending a pre-formatted string value. +func RFC3339NanoTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + encodeTimeLayout(t, time.RFC3339Nano, enc) +} + +// TimeEncoderOfLayout returns TimeEncoder which serializes a time.Time using +// given layout. +func TimeEncoderOfLayout(layout string) TimeEncoder { + return func(t time.Time, enc PrimitiveArrayEncoder) { + encodeTimeLayout(t, layout, enc) + } +} + +// UnmarshalText unmarshals text to a TimeEncoder. +// "rfc3339nano" and "RFC3339Nano" are unmarshaled to RFC3339NanoTimeEncoder. +// "rfc3339" and "RFC3339" are unmarshaled to RFC3339TimeEncoder. +// "iso8601" and "ISO8601" are unmarshaled to ISO8601TimeEncoder. +// "millis" is unmarshaled to EpochMillisTimeEncoder. +// "nanos" is unmarshaled to EpochNanosEncoder. +// Anything else is unmarshaled to EpochTimeEncoder. +func (e *TimeEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "rfc3339nano", "RFC3339Nano": + *e = RFC3339NanoTimeEncoder + case "rfc3339", "RFC3339": + *e = RFC3339TimeEncoder + case "iso8601", "ISO8601": + *e = ISO8601TimeEncoder + case "millis": + *e = EpochMillisTimeEncoder + case "nanos": + *e = EpochNanosTimeEncoder + default: + *e = EpochTimeEncoder + } + return nil +} + +// UnmarshalYAML unmarshals YAML to a TimeEncoder. +// If value is an object with a "layout" field, it will be unmarshaled to TimeEncoder with given layout. +// +// timeEncoder: +// layout: 06/01/02 03:04pm +// +// If value is string, it uses UnmarshalText. +// +// timeEncoder: iso8601 +func (e *TimeEncoder) UnmarshalYAML(unmarshal func(interface{}) error) error { + var o struct { + Layout string `json:"layout" yaml:"layout"` + } + if err := unmarshal(&o); err == nil { + *e = TimeEncoderOfLayout(o.Layout) + return nil + } + + var s string + if err := unmarshal(&s); err != nil { + return err + } + return e.UnmarshalText([]byte(s)) +} + +// UnmarshalJSON unmarshals JSON to a TimeEncoder as same way UnmarshalYAML does. +func (e *TimeEncoder) UnmarshalJSON(data []byte) error { + return e.UnmarshalYAML(func(v interface{}) error { + return json.Unmarshal(data, v) + }) +} + +// A DurationEncoder serializes a time.Duration to a primitive type. +type DurationEncoder func(time.Duration, PrimitiveArrayEncoder) + +// SecondsDurationEncoder serializes a time.Duration to a floating-point number of seconds elapsed. +func SecondsDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { + enc.AppendFloat64(float64(d) / float64(time.Second)) +} + +// NanosDurationEncoder serializes a time.Duration to an integer number of +// nanoseconds elapsed. +func NanosDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { + enc.AppendInt64(int64(d)) +} + +// MillisDurationEncoder serializes a time.Duration to an integer number of +// milliseconds elapsed. +func MillisDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { + enc.AppendInt64(d.Nanoseconds() / 1e6) +} + +// StringDurationEncoder serializes a time.Duration using its built-in String +// method. +func StringDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { + enc.AppendString(d.String()) +} + +// UnmarshalText unmarshals text to a DurationEncoder. "string" is unmarshaled +// to StringDurationEncoder, and anything else is unmarshaled to +// NanosDurationEncoder. +func (e *DurationEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "string": + *e = StringDurationEncoder + case "nanos": + *e = NanosDurationEncoder + case "ms": + *e = MillisDurationEncoder + default: + *e = SecondsDurationEncoder + } + return nil +} + +// A CallerEncoder serializes an EntryCaller to a primitive type. +type CallerEncoder func(EntryCaller, PrimitiveArrayEncoder) + +// FullCallerEncoder serializes a caller in /full/path/to/package/file:line +// format. +func FullCallerEncoder(caller EntryCaller, enc PrimitiveArrayEncoder) { + // TODO: consider using a byte-oriented API to save an allocation. + enc.AppendString(caller.String()) +} + +// ShortCallerEncoder serializes a caller in package/file:line format, trimming +// all but the final directory from the full path. +func ShortCallerEncoder(caller EntryCaller, enc PrimitiveArrayEncoder) { + // TODO: consider using a byte-oriented API to save an allocation. + enc.AppendString(caller.TrimmedPath()) +} + +// UnmarshalText unmarshals text to a CallerEncoder. "full" is unmarshaled to +// FullCallerEncoder and anything else is unmarshaled to ShortCallerEncoder. +func (e *CallerEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "full": + *e = FullCallerEncoder + default: + *e = ShortCallerEncoder + } + return nil +} + +// A NameEncoder serializes a period-separated logger name to a primitive +// type. +type NameEncoder func(string, PrimitiveArrayEncoder) + +// FullNameEncoder serializes the logger name as-is. +func FullNameEncoder(loggerName string, enc PrimitiveArrayEncoder) { + enc.AppendString(loggerName) +} + +// UnmarshalText unmarshals text to a NameEncoder. Currently, everything is +// unmarshaled to FullNameEncoder. +func (e *NameEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "full": + *e = FullNameEncoder + default: + *e = FullNameEncoder + } + return nil +} + +// An EncoderConfig allows users to configure the concrete encoders supplied by +// zapcore. +type EncoderConfig struct { + // Set the keys used for each log entry. If any key is empty, that portion + // of the entry is omitted. + MessageKey string `json:"messageKey" yaml:"messageKey"` + LevelKey string `json:"levelKey" yaml:"levelKey"` + TimeKey string `json:"timeKey" yaml:"timeKey"` + NameKey string `json:"nameKey" yaml:"nameKey"` + CallerKey string `json:"callerKey" yaml:"callerKey"` + FunctionKey string `json:"functionKey" yaml:"functionKey"` + StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"` + SkipLineEnding bool `json:"skipLineEnding" yaml:"skipLineEnding"` + LineEnding string `json:"lineEnding" yaml:"lineEnding"` + // Configure the primitive representations of common complex types. For + // example, some users may want all time.Times serialized as floating-point + // seconds since epoch, while others may prefer ISO8601 strings. + EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"` + EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"` + EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"` + EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"` + // Unlike the other primitive type encoders, EncodeName is optional. The + // zero value falls back to FullNameEncoder. + EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"` + // Configure the encoder for interface{} type objects. + // If not provided, objects are encoded using json.Encoder + NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"` + // Configures the field separator used by the console encoder. Defaults + // to tab. + ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"` +} + +// ObjectEncoder is a strongly-typed, encoding-agnostic interface for adding a +// map- or struct-like object to the logging context. Like maps, ObjectEncoders +// aren't safe for concurrent use (though typical use shouldn't require locks). +type ObjectEncoder interface { + // Logging-specific marshalers. + AddArray(key string, marshaler ArrayMarshaler) error + AddObject(key string, marshaler ObjectMarshaler) error + + // Built-in types. + AddBinary(key string, value []byte) // for arbitrary bytes + AddByteString(key string, value []byte) // for UTF-8 encoded bytes + AddBool(key string, value bool) + AddComplex128(key string, value complex128) + AddComplex64(key string, value complex64) + AddDuration(key string, value time.Duration) + AddFloat64(key string, value float64) + AddFloat32(key string, value float32) + AddInt(key string, value int) + AddInt64(key string, value int64) + AddInt32(key string, value int32) + AddInt16(key string, value int16) + AddInt8(key string, value int8) + AddString(key, value string) + AddTime(key string, value time.Time) + AddUint(key string, value uint) + AddUint64(key string, value uint64) + AddUint32(key string, value uint32) + AddUint16(key string, value uint16) + AddUint8(key string, value uint8) + AddUintptr(key string, value uintptr) + + // AddReflected uses reflection to serialize arbitrary objects, so it can be + // slow and allocation-heavy. + AddReflected(key string, value interface{}) error + // OpenNamespace opens an isolated namespace where all subsequent fields will + // be added. Applications can use namespaces to prevent key collisions when + // injecting loggers into sub-components or third-party libraries. + OpenNamespace(key string) +} + +// ArrayEncoder is a strongly-typed, encoding-agnostic interface for adding +// array-like objects to the logging context. Of note, it supports mixed-type +// arrays even though they aren't typical in Go. Like slices, ArrayEncoders +// aren't safe for concurrent use (though typical use shouldn't require locks). +type ArrayEncoder interface { + // Built-in types. + PrimitiveArrayEncoder + + // Time-related types. + AppendDuration(time.Duration) + AppendTime(time.Time) + + // Logging-specific marshalers. + AppendArray(ArrayMarshaler) error + AppendObject(ObjectMarshaler) error + + // AppendReflected uses reflection to serialize arbitrary objects, so it's + // slow and allocation-heavy. + AppendReflected(value interface{}) error +} + +// PrimitiveArrayEncoder is the subset of the ArrayEncoder interface that deals +// only in Go's built-in types. It's included only so that Duration- and +// TimeEncoders cannot trigger infinite recursion. +type PrimitiveArrayEncoder interface { + // Built-in types. + AppendBool(bool) + AppendByteString([]byte) // for UTF-8 encoded bytes + AppendComplex128(complex128) + AppendComplex64(complex64) + AppendFloat64(float64) + AppendFloat32(float32) + AppendInt(int) + AppendInt64(int64) + AppendInt32(int32) + AppendInt16(int16) + AppendInt8(int8) + AppendString(string) + AppendUint(uint) + AppendUint64(uint64) + AppendUint32(uint32) + AppendUint16(uint16) + AppendUint8(uint8) + AppendUintptr(uintptr) +} + +// Encoder is a format-agnostic interface for all log entry marshalers. Since +// log encoders don't need to support the same wide range of use cases as +// general-purpose marshalers, it's possible to make them faster and +// lower-allocation. +// +// Implementations of the ObjectEncoder interface's methods can, of course, +// freely modify the receiver. However, the Clone and EncodeEntry methods will +// be called concurrently and shouldn't modify the receiver. +type Encoder interface { + ObjectEncoder + + // Clone copies the encoder, ensuring that adding fields to the copy doesn't + // affect the original. + Clone() Encoder + + // EncodeEntry encodes an entry and fields, along with any accumulated + // context, into a byte buffer and returns it. Any fields that are empty, + // including fields on the `Entry` type, should be omitted. + EncodeEntry(Entry, []Field) (*buffer.Buffer, error) +} diff --git a/vendor/go.uber.org/zap/zapcore/encoder_test.go b/vendor/go.uber.org/zap/zapcore/encoder_test.go new file mode 100644 index 0000000000..9b8142f5d4 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/encoder_test.go @@ -0,0 +1,733 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "encoding/json" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" + + . "go.uber.org/zap/zapcore" +) + +var ( + _epoch = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC) + _testEntry = Entry{ + LoggerName: "main", + Level: InfoLevel, + Message: `hello`, + Time: _epoch, + Stack: "fake-stack", + Caller: EntryCaller{Defined: true, File: "foo.go", Line: 42, Function: "foo.Foo"}, + } +) + +func testEncoderConfig() EncoderConfig { + return EncoderConfig{ + MessageKey: "msg", + LevelKey: "level", + NameKey: "name", + TimeKey: "ts", + CallerKey: "caller", + FunctionKey: "func", + StacktraceKey: "stacktrace", + LineEnding: "\n", + EncodeTime: EpochTimeEncoder, + EncodeLevel: LowercaseLevelEncoder, + EncodeDuration: SecondsDurationEncoder, + EncodeCaller: ShortCallerEncoder, + } +} + +func humanEncoderConfig() EncoderConfig { + cfg := testEncoderConfig() + cfg.EncodeTime = ISO8601TimeEncoder + cfg.EncodeLevel = CapitalLevelEncoder + cfg.EncodeDuration = StringDurationEncoder + return cfg +} + +func capitalNameEncoder(loggerName string, enc PrimitiveArrayEncoder) { + enc.AppendString(strings.ToUpper(loggerName)) +} + +func TestEncoderConfiguration(t *testing.T) { + base := testEncoderConfig() + + tests := []struct { + desc string + cfg EncoderConfig + amendEntry func(Entry) Entry + extra func(Encoder) + expectedJSON string + expectedConsole string + }{ + { + desc: "messages to be escaped", + cfg: base, + amendEntry: func(ent Entry) Entry { + ent.Message = `hello\` + return ent + }, + expectedJSON: `{"level":"info","ts":0,"name":"main","caller":"foo.go:42","func":"foo.Foo","msg":"hello\\","stacktrace":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\\\nfake-stack\n", + }, + { + desc: "use custom entry keys in JSON output and ignore them in console output", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "skip line ending if SkipLineEnding is 'true'", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + SkipLineEnding: true, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}`, + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack", + }, + { + desc: "skip level if LevelKey is omitted", + cfg: EncoderConfig{ + LevelKey: OmitKey, + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "skip timestamp if TimeKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: OmitKey, + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "info\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "skip message if MessageKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: OmitKey, + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\nfake-stack\n", + }, + { + desc: "skip name if NameKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: OmitKey, + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "skip caller if CallerKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: OmitKey, + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "skip function if FunctionKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: OmitKey, + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\nfake-stack\n", + }, + { + desc: "skip stacktrace if StacktraceKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: OmitKey, + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\n", + }, + { + desc: "use the supplied EncodeTime, for both the entry and any times added", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: func(t time.Time, enc PrimitiveArrayEncoder) { enc.AppendString(t.String()) }, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + extra: func(enc Encoder) { + enc.AddTime("extra", _epoch) + err := enc.AddArray("extras", ArrayMarshalerFunc(func(enc ArrayEncoder) error { + enc.AppendTime(_epoch) + return nil + })) + assert.NoError(t, err) + }, + expectedJSON: `{"L":"info","T":"1970-01-01 00:00:00 +0000 UTC","N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","extra":"1970-01-01 00:00:00 +0000 UTC","extras":["1970-01-01 00:00:00 +0000 UTC"],"S":"fake-stack"}` + "\n", + expectedConsole: "1970-01-01 00:00:00 +0000 UTC\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + // plain-text preamble + `{"extra": "1970-01-01 00:00:00 +0000 UTC", "extras": ["1970-01-01 00:00:00 +0000 UTC"]}` + // JSON context + "\nfake-stack\n", // stacktrace after newline + }, + { + desc: "use the supplied EncodeDuration for any durations added", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: StringDurationEncoder, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + extra: func(enc Encoder) { + enc.AddDuration("extra", time.Second) + err := enc.AddArray("extras", ArrayMarshalerFunc(func(enc ArrayEncoder) error { + enc.AppendDuration(time.Minute) + return nil + })) + assert.NoError(t, err) + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","extra":"1s","extras":["1m0s"],"S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + // preamble + `{"extra": "1s", "extras": ["1m0s"]}` + // context + "\nfake-stack\n", // stacktrace + }, + { + desc: "use the supplied EncodeLevel", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: CapitalLevelEncoder, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"INFO","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tINFO\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "use the supplied EncodeName", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeName: capitalNameEncoder, + }, + expectedJSON: `{"L":"info","T":0,"N":"MAIN","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tMAIN\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "close all open namespaces", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + extra: func(enc Encoder) { + enc.OpenNamespace("outer") + enc.OpenNamespace("inner") + enc.AddString("foo", "bar") + enc.OpenNamespace("innermost") + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","outer":{"inner":{"foo":"bar","innermost":{}}},"S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + + `{"outer": {"inner": {"foo": "bar", "innermost": {}}}}` + + "\nfake-stack\n", + }, + { + desc: "handle no-op EncodeTime", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: func(time.Time, PrimitiveArrayEncoder) {}, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + extra: func(enc Encoder) { enc.AddTime("sometime", time.Unix(0, 100)) }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","sometime":100,"S":"fake-stack"}` + "\n", + expectedConsole: "info\tmain\tfoo.go:42\tfoo.Foo\thello\t" + `{"sometime": 100}` + "\nfake-stack\n", + }, + { + desc: "handle no-op EncodeDuration", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: func(time.Duration, PrimitiveArrayEncoder) {}, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + extra: func(enc Encoder) { enc.AddDuration("someduration", time.Microsecond) }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","someduration":1000,"S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + `{"someduration": 1000}` + "\nfake-stack\n", + }, + { + desc: "handle no-op EncodeLevel", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: func(Level, PrimitiveArrayEncoder) {}, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "handle no-op EncodeCaller", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: func(EntryCaller, PrimitiveArrayEncoder) {}, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "handle no-op EncodeName", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeName: func(string, PrimitiveArrayEncoder) {}, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "use custom line separator", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: "\r\n", + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\r\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\r\n", + }, + { + desc: "omit line separator definition - fall back to default", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + DefaultLineEnding, + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack" + DefaultLineEnding, + }, + } + + for i, tt := range tests { + json := NewJSONEncoder(tt.cfg) + console := NewConsoleEncoder(tt.cfg) + if tt.extra != nil { + tt.extra(json) + tt.extra(console) + } + entry := _testEntry + if tt.amendEntry != nil { + entry = tt.amendEntry(_testEntry) + } + jsonOut, jsonErr := json.EncodeEntry(entry, nil) + if assert.NoError(t, jsonErr, "Unexpected error JSON-encoding entry in case #%d.", i) { + assert.Equal( + t, + tt.expectedJSON, + jsonOut.String(), + "Unexpected JSON output: expected to %v.", tt.desc, + ) + } + consoleOut, consoleErr := console.EncodeEntry(entry, nil) + if assert.NoError(t, consoleErr, "Unexpected error console-encoding entry in case #%d.", i) { + assert.Equal( + t, + tt.expectedConsole, + consoleOut.String(), + "Unexpected console output: expected to %v.", tt.desc, + ) + } + } +} + +func TestLevelEncoders(t *testing.T) { + tests := []struct { + name string + expected interface{} // output of encoding InfoLevel + }{ + {"capital", "INFO"}, + {"lower", "info"}, + {"", "info"}, + {"something-random", "info"}, + } + + for _, tt := range tests { + var le LevelEncoder + require.NoError(t, le.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { le(InfoLevel, arr) }, + "Unexpected output serializing InfoLevel with %q.", tt.name, + ) + } +} + +func TestTimeEncoders(t *testing.T) { + moment := time.Unix(100, 50005000).UTC() + tests := []struct { + yamlDoc string + expected interface{} // output of serializing moment + }{ + {"timeEncoder: iso8601", "1970-01-01T00:01:40.050Z"}, + {"timeEncoder: ISO8601", "1970-01-01T00:01:40.050Z"}, + {"timeEncoder: millis", 100050.005}, + {"timeEncoder: nanos", int64(100050005000)}, + {"timeEncoder: {layout: 06/01/02 03:04pm}", "70/01/01 12:01am"}, + {"timeEncoder: ''", 100.050005}, + {"timeEncoder: something-random", 100.050005}, + {"timeEncoder: rfc3339", "1970-01-01T00:01:40Z"}, + {"timeEncoder: RFC3339", "1970-01-01T00:01:40Z"}, + {"timeEncoder: rfc3339nano", "1970-01-01T00:01:40.050005Z"}, + {"timeEncoder: RFC3339Nano", "1970-01-01T00:01:40.050005Z"}, + } + + for _, tt := range tests { + cfg := EncoderConfig{} + require.NoError(t, yaml.Unmarshal([]byte(tt.yamlDoc), &cfg), "Unexpected error unmarshaling %q.", tt.yamlDoc) + require.NotNil(t, cfg.EncodeTime, "Unmashalled timeEncoder is nil for %q.", tt.yamlDoc) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { cfg.EncodeTime(moment, arr) }, + "Unexpected output serializing %v with %q.", moment, tt.yamlDoc, + ) + } +} + +func TestTimeEncodersWrongYAML(t *testing.T) { + tests := []string{ + "timeEncoder: [1, 2, 3]", // wrong type + "timeEncoder: {foo:bar", // broken yaml + } + for _, tt := range tests { + cfg := EncoderConfig{} + assert.Error(t, yaml.Unmarshal([]byte(tt), &cfg), "Expected unmarshaling %q to become error, but not.", tt) + } +} + +func TestTimeEncodersParseFromJSON(t *testing.T) { + moment := time.Unix(100, 50005000).UTC() + tests := []struct { + jsonDoc string + expected interface{} // output of serializing moment + }{ + {`{"timeEncoder": "iso8601"}`, "1970-01-01T00:01:40.050Z"}, + {`{"timeEncoder": {"layout": "06/01/02 03:04pm"}}`, "70/01/01 12:01am"}, + } + + for _, tt := range tests { + cfg := EncoderConfig{} + require.NoError(t, json.Unmarshal([]byte(tt.jsonDoc), &cfg), "Unexpected error unmarshaling %q.", tt.jsonDoc) + require.NotNil(t, cfg.EncodeTime, "Unmashalled timeEncoder is nil for %q.", tt.jsonDoc) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { cfg.EncodeTime(moment, arr) }, + "Unexpected output serializing %v with %q.", moment, tt.jsonDoc, + ) + } +} + +func TestDurationEncoders(t *testing.T) { + elapsed := time.Second + 500*time.Nanosecond + tests := []struct { + name string + expected interface{} // output of serializing elapsed + }{ + {"string", "1.0000005s"}, + {"nanos", int64(1000000500)}, + {"ms", int64(1000)}, + {"", 1.0000005}, + {"something-random", 1.0000005}, + } + + for _, tt := range tests { + var de DurationEncoder + require.NoError(t, de.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { de(elapsed, arr) }, + "Unexpected output serializing %v with %q.", elapsed, tt.name, + ) + } +} + +func TestCallerEncoders(t *testing.T) { + caller := EntryCaller{Defined: true, File: "/home/jack/src/github.com/foo/foo.go", Line: 42} + tests := []struct { + name string + expected interface{} // output of serializing caller + }{ + {"", "foo/foo.go:42"}, + {"something-random", "foo/foo.go:42"}, + {"short", "foo/foo.go:42"}, + {"full", "/home/jack/src/github.com/foo/foo.go:42"}, + } + + for _, tt := range tests { + var ce CallerEncoder + require.NoError(t, ce.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { ce(caller, arr) }, + "Unexpected output serializing file name as %v with %q.", tt.expected, tt.name, + ) + } +} + +func TestNameEncoders(t *testing.T) { + tests := []struct { + name string + expected interface{} // output of encoding InfoLevel + }{ + {"", "main"}, + {"full", "main"}, + {"something-random", "main"}, + } + + for _, tt := range tests { + var ne NameEncoder + require.NoError(t, ne.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { ne("main", arr) }, + "Unexpected output serializing logger name with %q.", tt.name, + ) + } +} + +func assertAppended(t testing.TB, expected interface{}, f func(ArrayEncoder), msgAndArgs ...interface{}) { + mem := NewMapObjectEncoder() + err := mem.AddArray("k", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + f(arr) + return nil + })) + assert.NoError(t, err, msgAndArgs...) + arr := mem.Fields["k"].([]interface{}) + require.Equal(t, 1, len(arr), "Expected to append exactly one element to array.") + assert.Equal(t, expected, arr[0], msgAndArgs...) +} diff --git a/vendor/go.uber.org/zap/zapcore/entry.go b/vendor/go.uber.org/zap/zapcore/entry.go new file mode 100644 index 0000000000..459a5d7ce3 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/entry.go @@ -0,0 +1,298 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "fmt" + "runtime" + "strings" + "time" + + "go.uber.org/multierr" + "go.uber.org/zap/internal/bufferpool" + "go.uber.org/zap/internal/exit" + "go.uber.org/zap/internal/pool" +) + +var _cePool = pool.New(func() *CheckedEntry { + // Pre-allocate some space for cores. + return &CheckedEntry{ + cores: make([]Core, 4), + } +}) + +func getCheckedEntry() *CheckedEntry { + ce := _cePool.Get() + ce.reset() + return ce +} + +func putCheckedEntry(ce *CheckedEntry) { + if ce == nil { + return + } + _cePool.Put(ce) +} + +// NewEntryCaller makes an EntryCaller from the return signature of +// runtime.Caller. +func NewEntryCaller(pc uintptr, file string, line int, ok bool) EntryCaller { + if !ok { + return EntryCaller{} + } + return EntryCaller{ + PC: pc, + File: file, + Line: line, + Defined: true, + } +} + +// EntryCaller represents the caller of a logging function. +type EntryCaller struct { + Defined bool + PC uintptr + File string + Line int + Function string +} + +// String returns the full path and line number of the caller. +func (ec EntryCaller) String() string { + return ec.FullPath() +} + +// FullPath returns a /full/path/to/package/file:line description of the +// caller. +func (ec EntryCaller) FullPath() string { + if !ec.Defined { + return "undefined" + } + buf := bufferpool.Get() + buf.AppendString(ec.File) + buf.AppendByte(':') + buf.AppendInt(int64(ec.Line)) + caller := buf.String() + buf.Free() + return caller +} + +// TrimmedPath returns a package/file:line description of the caller, +// preserving only the leaf directory name and file name. +func (ec EntryCaller) TrimmedPath() string { + if !ec.Defined { + return "undefined" + } + // nb. To make sure we trim the path correctly on Windows too, we + // counter-intuitively need to use '/' and *not* os.PathSeparator here, + // because the path given originates from Go stdlib, specifically + // runtime.Caller() which (as of Mar/17) returns forward slashes even on + // Windows. + // + // See https://github.com/golang/go/issues/3335 + // and https://github.com/golang/go/issues/18151 + // + // for discussion on the issue on Go side. + // + // Find the last separator. + // + idx := strings.LastIndexByte(ec.File, '/') + if idx == -1 { + return ec.FullPath() + } + // Find the penultimate separator. + idx = strings.LastIndexByte(ec.File[:idx], '/') + if idx == -1 { + return ec.FullPath() + } + buf := bufferpool.Get() + // Keep everything after the penultimate separator. + buf.AppendString(ec.File[idx+1:]) + buf.AppendByte(':') + buf.AppendInt(int64(ec.Line)) + caller := buf.String() + buf.Free() + return caller +} + +// An Entry represents a complete log message. The entry's structured context +// is already serialized, but the log level, time, message, and call site +// information are available for inspection and modification. Any fields left +// empty will be omitted when encoding. +// +// Entries are pooled, so any functions that accept them MUST be careful not to +// retain references to them. +type Entry struct { + Level Level + Time time.Time + LoggerName string + Message string + Caller EntryCaller + Stack string +} + +// CheckWriteHook is a custom action that may be executed after an entry is +// written. +// +// Register one on a CheckedEntry with the After method. +// +// if ce := logger.Check(...); ce != nil { +// ce = ce.After(hook) +// ce.Write(...) +// } +// +// You can configure the hook for Fatal log statements at the logger level with +// the zap.WithFatalHook option. +type CheckWriteHook interface { + // OnWrite is invoked with the CheckedEntry that was written and a list + // of fields added with that entry. + // + // The list of fields DOES NOT include fields that were already added + // to the logger with the With method. + OnWrite(*CheckedEntry, []Field) +} + +// CheckWriteAction indicates what action to take after a log entry is +// processed. Actions are ordered in increasing severity. +type CheckWriteAction uint8 + +const ( + // WriteThenNoop indicates that nothing special needs to be done. It's the + // default behavior. + WriteThenNoop CheckWriteAction = iota + // WriteThenGoexit runs runtime.Goexit after Write. + WriteThenGoexit + // WriteThenPanic causes a panic after Write. + WriteThenPanic + // WriteThenFatal causes an os.Exit(1) after Write. + WriteThenFatal +) + +// OnWrite implements the OnWrite method to keep CheckWriteAction compatible +// with the new CheckWriteHook interface which deprecates CheckWriteAction. +func (a CheckWriteAction) OnWrite(ce *CheckedEntry, _ []Field) { + switch a { + case WriteThenGoexit: + runtime.Goexit() + case WriteThenPanic: + panic(ce.Message) + case WriteThenFatal: + exit.With(1) + } +} + +var _ CheckWriteHook = CheckWriteAction(0) + +// CheckedEntry is an Entry together with a collection of Cores that have +// already agreed to log it. +// +// CheckedEntry references should be created by calling AddCore or After on a +// nil *CheckedEntry. References are returned to a pool after Write, and MUST +// NOT be retained after calling their Write method. +type CheckedEntry struct { + Entry + ErrorOutput WriteSyncer + dirty bool // best-effort detection of pool misuse + after CheckWriteHook + cores []Core +} + +func (ce *CheckedEntry) reset() { + ce.Entry = Entry{} + ce.ErrorOutput = nil + ce.dirty = false + ce.after = nil + for i := range ce.cores { + // don't keep references to cores + ce.cores[i] = nil + } + ce.cores = ce.cores[:0] +} + +// Write writes the entry to the stored Cores, returns any errors, and returns +// the CheckedEntry reference to a pool for immediate re-use. Finally, it +// executes any required CheckWriteAction. +func (ce *CheckedEntry) Write(fields ...Field) { + if ce == nil { + return + } + + if ce.dirty { + if ce.ErrorOutput != nil { + // Make a best effort to detect unsafe re-use of this CheckedEntry. + // If the entry is dirty, log an internal error; because the + // CheckedEntry is being used after it was returned to the pool, + // the message may be an amalgamation from multiple call sites. + fmt.Fprintf(ce.ErrorOutput, "%v Unsafe CheckedEntry re-use near Entry %+v.\n", ce.Time, ce.Entry) + _ = ce.ErrorOutput.Sync() // ignore error + } + return + } + ce.dirty = true + + var err error + for i := range ce.cores { + err = multierr.Append(err, ce.cores[i].Write(ce.Entry, fields)) + } + if err != nil && ce.ErrorOutput != nil { + fmt.Fprintf(ce.ErrorOutput, "%v write error: %v\n", ce.Time, err) + _ = ce.ErrorOutput.Sync() // ignore error + } + + hook := ce.after + if hook != nil { + hook.OnWrite(ce, fields) + } + putCheckedEntry(ce) +} + +// AddCore adds a Core that has agreed to log this CheckedEntry. It's intended to be +// used by Core.Check implementations, and is safe to call on nil CheckedEntry +// references. +func (ce *CheckedEntry) AddCore(ent Entry, core Core) *CheckedEntry { + if ce == nil { + ce = getCheckedEntry() + ce.Entry = ent + } + ce.cores = append(ce.cores, core) + return ce +} + +// Should sets this CheckedEntry's CheckWriteAction, which controls whether a +// Core will panic or fatal after writing this log entry. Like AddCore, it's +// safe to call on nil CheckedEntry references. +// +// Deprecated: Use [CheckedEntry.After] instead. +func (ce *CheckedEntry) Should(ent Entry, should CheckWriteAction) *CheckedEntry { + return ce.After(ent, should) +} + +// After sets this CheckEntry's CheckWriteHook, which will be called after this +// log entry has been written. It's safe to call this on nil CheckedEntry +// references. +func (ce *CheckedEntry) After(ent Entry, hook CheckWriteHook) *CheckedEntry { + if ce == nil { + ce = getCheckedEntry() + ce.Entry = ent + } + ce.after = hook + return ce +} diff --git a/vendor/go.uber.org/zap/zapcore/entry_test.go b/vendor/go.uber.org/zap/zapcore/entry_test.go new file mode 100644 index 0000000000..6555ab6d08 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/entry_test.go @@ -0,0 +1,149 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "sync" + "testing" + + "go.uber.org/zap/internal/exit" + + "github.com/stretchr/testify/assert" +) + +func assertGoexit(t *testing.T, f func()) { + var finished bool + recovered := make(chan interface{}) + go func() { + defer func() { + recovered <- recover() + }() + + f() + finished = true + }() + + assert.Nil(t, <-recovered, "Goexit should cause recover to return nil") + assert.False(t, finished, "Goroutine should not finish after Goexit") +} + +func TestPutNilEntry(t *testing.T) { + // Pooling nil entries defeats the purpose. + var wg sync.WaitGroup + wg.Add(2) + + go func() { + defer wg.Done() + for i := 0; i < 1000; i++ { + putCheckedEntry(nil) + } + }() + + go func() { + defer wg.Done() + for i := 0; i < 1000; i++ { + ce := getCheckedEntry() + assert.NotNil(t, ce, "Expected only non-nil CheckedEntries in pool.") + assert.False(t, ce.dirty, "Unexpected dirty bit set.") + assert.Nil(t, ce.ErrorOutput, "Non-nil ErrorOutput.") + assert.Nil(t, ce.after, "Unexpected terminal behavior.") + assert.Equal(t, 0, len(ce.cores), "Expected empty slice of cores.") + assert.True(t, cap(ce.cores) > 0, "Expected pooled CheckedEntries to pre-allocate slice of Cores.") + } + }() + + wg.Wait() +} + +func TestEntryCaller(t *testing.T) { + tests := []struct { + caller EntryCaller + full string + short string + }{ + { + caller: NewEntryCaller(100, "/path/to/foo.go", 42, false), + full: "undefined", + short: "undefined", + }, + { + caller: NewEntryCaller(100, "/path/to/foo.go", 42, true), + full: "/path/to/foo.go:42", + short: "to/foo.go:42", + }, + { + caller: NewEntryCaller(100, "to/foo.go", 42, true), + full: "to/foo.go:42", + short: "to/foo.go:42", + }, + } + + for _, tt := range tests { + assert.Equal(t, tt.full, tt.caller.String(), "Unexpected string from EntryCaller.") + assert.Equal(t, tt.full, tt.caller.FullPath(), "Unexpected FullPath from EntryCaller.") + assert.Equal(t, tt.short, tt.caller.TrimmedPath(), "Unexpected TrimmedPath from EntryCaller.") + } +} + +func TestCheckedEntryWrite(t *testing.T) { + t.Run("nil is safe", func(t *testing.T) { + var ce *CheckedEntry + assert.NotPanics(t, func() { ce.Write() }, "Unexpected panic writing nil CheckedEntry.") + }) + + t.Run("WriteThenPanic", func(t *testing.T) { + var ce *CheckedEntry + ce = ce.After(Entry{}, WriteThenPanic) + assert.Panics(t, func() { ce.Write() }, "Expected to panic when WriteThenPanic is set.") + }) + + t.Run("WriteThenGoexit", func(t *testing.T) { + var ce *CheckedEntry + ce = ce.After(Entry{}, WriteThenGoexit) + assertGoexit(t, func() { ce.Write() }) + }) + + t.Run("WriteThenFatal", func(t *testing.T) { + var ce *CheckedEntry + ce = ce.After(Entry{}, WriteThenFatal) + stub := exit.WithStub(func() { + ce.Write() + }) + assert.True(t, stub.Exited, "Expected to exit when WriteThenFatal is set.") + assert.Equal(t, 1, stub.Code, "Expected to exit when WriteThenFatal is set.") + }) + + t.Run("After", func(t *testing.T) { + var ce *CheckedEntry + hook := &customHook{} + ce = ce.After(Entry{}, hook) + ce.Write() + assert.True(t, hook.called, "Expected to call custom action after Write.") + }) +} + +type customHook struct { + called bool +} + +func (c *customHook) OnWrite(_ *CheckedEntry, _ []Field) { + c.called = true +} diff --git a/vendor/go.uber.org/zap/zapcore/error.go b/vendor/go.uber.org/zap/zapcore/error.go new file mode 100644 index 0000000000..c40df13269 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/error.go @@ -0,0 +1,136 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "fmt" + "reflect" + + "go.uber.org/zap/internal/pool" +) + +// Encodes the given error into fields of an object. A field with the given +// name is added for the error message. +// +// If the error implements fmt.Formatter, a field with the name ${key}Verbose +// is also added with the full verbose error message. +// +// Finally, if the error implements errorGroup (from go.uber.org/multierr) or +// causer (from github.com/pkg/errors), a ${key}Causes field is added with an +// array of objects containing the errors this error was comprised of. +// +// { +// "error": err.Error(), +// "errorVerbose": fmt.Sprintf("%+v", err), +// "errorCauses": [ +// ... +// ], +// } +func encodeError(key string, err error, enc ObjectEncoder) (retErr error) { + // Try to capture panics (from nil references or otherwise) when calling + // the Error() method + defer func() { + if rerr := recover(); rerr != nil { + // If it's a nil pointer, just say "<nil>". The likeliest causes are a + // error that fails to guard against nil or a nil pointer for a + // value receiver, and in either case, "<nil>" is a nice result. + if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() { + enc.AddString(key, "<nil>") + return + } + + retErr = fmt.Errorf("PANIC=%v", rerr) + } + }() + + basic := err.Error() + enc.AddString(key, basic) + + switch e := err.(type) { + case errorGroup: + return enc.AddArray(key+"Causes", errArray(e.Errors())) + case fmt.Formatter: + verbose := fmt.Sprintf("%+v", e) + if verbose != basic { + // This is a rich error type, like those produced by + // github.com/pkg/errors. + enc.AddString(key+"Verbose", verbose) + } + } + return nil +} + +type errorGroup interface { + // Provides read-only access to the underlying list of errors, preferably + // without causing any allocs. + Errors() []error +} + +// Note that errArray and errArrayElem are very similar to the version +// implemented in the top-level error.go file. We can't re-use this because +// that would require exporting errArray as part of the zapcore API. + +// Encodes a list of errors using the standard error encoding logic. +type errArray []error + +func (errs errArray) MarshalLogArray(arr ArrayEncoder) error { + for i := range errs { + if errs[i] == nil { + continue + } + + el := newErrArrayElem(errs[i]) + err := arr.AppendObject(el) + el.Free() + if err != nil { + return err + } + } + return nil +} + +var _errArrayElemPool = pool.New(func() *errArrayElem { + return &errArrayElem{} +}) + +// Encodes any error into a {"error": ...} re-using the same errors logic. +// +// May be passed in place of an array to build a single-element array. +type errArrayElem struct{ err error } + +func newErrArrayElem(err error) *errArrayElem { + e := _errArrayElemPool.Get() + e.err = err + return e +} + +func (e *errArrayElem) MarshalLogArray(arr ArrayEncoder) error { + return arr.AppendObject(e) +} + +func (e *errArrayElem) MarshalLogObject(enc ObjectEncoder) error { + return encodeError("error", e.err, enc) +} + +func (e *errArrayElem) Free() { + e.err = nil + _errArrayElemPool.Put(e) +} diff --git a/vendor/go.uber.org/zap/zapcore/error_test.go b/vendor/go.uber.org/zap/zapcore/error_test.go new file mode 100644 index 0000000000..c5d61b0403 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/error_test.go @@ -0,0 +1,210 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "errors" + "fmt" + "io" + "testing" + + "github.com/stretchr/testify/assert" + + "go.uber.org/multierr" + "go.uber.org/zap/zapcore" + . "go.uber.org/zap/zapcore" +) + +type errTooManyUsers int + +func (e errTooManyUsers) Error() string { + return fmt.Sprintf("%d too many users", int(e)) +} + +func (e errTooManyUsers) Format(s fmt.State, verb rune) { + // Implement fmt.Formatter, but don't add any information beyond the basic + // Error method. + if verb == 'v' && s.Flag('+') { + io.WriteString(s, e.Error()) + } +} + +type customMultierr struct{} + +func (e customMultierr) Error() string { + return "great sadness" +} + +func (e customMultierr) Errors() []error { + return []error{ + errors.New("foo"), + nil, + multierr.Append( + errors.New("bar"), + errors.New("baz"), + ), + } +} + +func TestErrorEncoding(t *testing.T) { + tests := []struct { + k string + t FieldType // defaults to ErrorType + iface interface{} + want map[string]interface{} + }{ + { + k: "k", + iface: errTooManyUsers(2), + want: map[string]interface{}{ + "k": "2 too many users", + }, + }, + { + k: "err", + iface: multierr.Combine( + errors.New("foo"), + errors.New("bar"), + errors.New("baz"), + ), + want: map[string]interface{}{ + "err": "foo; bar; baz", + "errCauses": []interface{}{ + map[string]interface{}{"error": "foo"}, + map[string]interface{}{"error": "bar"}, + map[string]interface{}{"error": "baz"}, + }, + }, + }, + { + k: "e", + iface: customMultierr{}, + want: map[string]interface{}{ + "e": "great sadness", + "eCauses": []interface{}{ + map[string]interface{}{"error": "foo"}, + map[string]interface{}{ + "error": "bar; baz", + "errorCauses": []interface{}{ + map[string]interface{}{"error": "bar"}, + map[string]interface{}{"error": "baz"}, + }, + }, + }, + }, + }, + { + k: "k", + iface: fmt.Errorf("failed: %w", errors.New("egad")), + want: map[string]interface{}{ + "k": "failed: egad", + }, + }, + { + k: "error", + iface: multierr.Combine( + fmt.Errorf("hello: %w", + multierr.Combine(errors.New("foo"), errors.New("bar")), + ), + errors.New("baz"), + fmt.Errorf("world: %w", errors.New("qux")), + ), + want: map[string]interface{}{ + "error": "hello: foo; bar; baz; world: qux", + "errorCauses": []interface{}{ + map[string]interface{}{ + "error": "hello: foo; bar", + }, + map[string]interface{}{"error": "baz"}, + map[string]interface{}{"error": "world: qux"}, + }, + }, + }, + } + + for _, tt := range tests { + if tt.t == UnknownType { + tt.t = ErrorType + } + + enc := NewMapObjectEncoder() + f := Field{Key: tt.k, Type: tt.t, Interface: tt.iface} + f.AddTo(enc) + assert.Equal(t, tt.want, enc.Fields, "Unexpected output from field %+v.", f) + } +} + +func TestRichErrorSupport(t *testing.T) { + f := Field{ + Type: ErrorType, + Interface: fmt.Errorf("failed: %w", errors.New("egad")), + Key: "k", + } + enc := NewMapObjectEncoder() + f.AddTo(enc) + assert.Equal(t, "failed: egad", enc.Fields["k"], "Unexpected basic error message.") +} + +func TestErrArrayBrokenEncoder(t *testing.T) { + t.Parallel() + + f := Field{ + Key: "foo", + Type: ErrorType, + Interface: multierr.Combine( + errors.New("foo"), + errors.New("bar"), + ), + } + + failWith := errors.New("great sadness") + enc := NewMapObjectEncoder() + f.AddTo(brokenArrayObjectEncoder{ + Err: failWith, + ObjectEncoder: enc, + }) + + // Failure to add the field to the encoder + // causes the error to be added as a string field. + assert.Equal(t, "great sadness", enc.Fields["fooError"], + "Unexpected error message.") +} + +// brokenArrayObjectEncoder is an ObjectEncoder +// that builds a broken ArrayEncoder. +type brokenArrayObjectEncoder struct { + ObjectEncoder + ArrayEncoder + + Err error // error to return +} + +func (enc brokenArrayObjectEncoder) AddArray(key string, marshaler ArrayMarshaler) error { + return enc.ObjectEncoder.AddArray(key, + ArrayMarshalerFunc(func(ae ArrayEncoder) error { + enc.ArrayEncoder = ae + return marshaler.MarshalLogArray(enc) + })) +} + +func (enc brokenArrayObjectEncoder) AppendObject(zapcore.ObjectMarshaler) error { + return enc.Err +} diff --git a/vendor/go.uber.org/zap/zapcore/field.go b/vendor/go.uber.org/zap/zapcore/field.go new file mode 100644 index 0000000000..95bdb0a126 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/field.go @@ -0,0 +1,233 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bytes" + "fmt" + "math" + "reflect" + "time" +) + +// A FieldType indicates which member of the Field union struct should be used +// and how it should be serialized. +type FieldType uint8 + +const ( + // UnknownType is the default field type. Attempting to add it to an encoder will panic. + UnknownType FieldType = iota + // ArrayMarshalerType indicates that the field carries an ArrayMarshaler. + ArrayMarshalerType + // ObjectMarshalerType indicates that the field carries an ObjectMarshaler. + ObjectMarshalerType + // BinaryType indicates that the field carries an opaque binary blob. + BinaryType + // BoolType indicates that the field carries a bool. + BoolType + // ByteStringType indicates that the field carries UTF-8 encoded bytes. + ByteStringType + // Complex128Type indicates that the field carries a complex128. + Complex128Type + // Complex64Type indicates that the field carries a complex128. + Complex64Type + // DurationType indicates that the field carries a time.Duration. + DurationType + // Float64Type indicates that the field carries a float64. + Float64Type + // Float32Type indicates that the field carries a float32. + Float32Type + // Int64Type indicates that the field carries an int64. + Int64Type + // Int32Type indicates that the field carries an int32. + Int32Type + // Int16Type indicates that the field carries an int16. + Int16Type + // Int8Type indicates that the field carries an int8. + Int8Type + // StringType indicates that the field carries a string. + StringType + // TimeType indicates that the field carries a time.Time that is + // representable by a UnixNano() stored as an int64. + TimeType + // TimeFullType indicates that the field carries a time.Time stored as-is. + TimeFullType + // Uint64Type indicates that the field carries a uint64. + Uint64Type + // Uint32Type indicates that the field carries a uint32. + Uint32Type + // Uint16Type indicates that the field carries a uint16. + Uint16Type + // Uint8Type indicates that the field carries a uint8. + Uint8Type + // UintptrType indicates that the field carries a uintptr. + UintptrType + // ReflectType indicates that the field carries an interface{}, which should + // be serialized using reflection. + ReflectType + // NamespaceType signals the beginning of an isolated namespace. All + // subsequent fields should be added to the new namespace. + NamespaceType + // StringerType indicates that the field carries a fmt.Stringer. + StringerType + // ErrorType indicates that the field carries an error. + ErrorType + // SkipType indicates that the field is a no-op. + SkipType + + // InlineMarshalerType indicates that the field carries an ObjectMarshaler + // that should be inlined. + InlineMarshalerType +) + +// A Field is a marshaling operation used to add a key-value pair to a logger's +// context. Most fields are lazily marshaled, so it's inexpensive to add fields +// to disabled debug-level log statements. +type Field struct { + Key string + Type FieldType + Integer int64 + String string + Interface interface{} +} + +// AddTo exports a field through the ObjectEncoder interface. It's primarily +// useful to library authors, and shouldn't be necessary in most applications. +func (f Field) AddTo(enc ObjectEncoder) { + var err error + + switch f.Type { + case ArrayMarshalerType: + err = enc.AddArray(f.Key, f.Interface.(ArrayMarshaler)) + case ObjectMarshalerType: + err = enc.AddObject(f.Key, f.Interface.(ObjectMarshaler)) + case InlineMarshalerType: + err = f.Interface.(ObjectMarshaler).MarshalLogObject(enc) + case BinaryType: + enc.AddBinary(f.Key, f.Interface.([]byte)) + case BoolType: + enc.AddBool(f.Key, f.Integer == 1) + case ByteStringType: + enc.AddByteString(f.Key, f.Interface.([]byte)) + case Complex128Type: + enc.AddComplex128(f.Key, f.Interface.(complex128)) + case Complex64Type: + enc.AddComplex64(f.Key, f.Interface.(complex64)) + case DurationType: + enc.AddDuration(f.Key, time.Duration(f.Integer)) + case Float64Type: + enc.AddFloat64(f.Key, math.Float64frombits(uint64(f.Integer))) + case Float32Type: + enc.AddFloat32(f.Key, math.Float32frombits(uint32(f.Integer))) + case Int64Type: + enc.AddInt64(f.Key, f.Integer) + case Int32Type: + enc.AddInt32(f.Key, int32(f.Integer)) + case Int16Type: + enc.AddInt16(f.Key, int16(f.Integer)) + case Int8Type: + enc.AddInt8(f.Key, int8(f.Integer)) + case StringType: + enc.AddString(f.Key, f.String) + case TimeType: + if f.Interface != nil { + enc.AddTime(f.Key, time.Unix(0, f.Integer).In(f.Interface.(*time.Location))) + } else { + // Fall back to UTC if location is nil. + enc.AddTime(f.Key, time.Unix(0, f.Integer)) + } + case TimeFullType: + enc.AddTime(f.Key, f.Interface.(time.Time)) + case Uint64Type: + enc.AddUint64(f.Key, uint64(f.Integer)) + case Uint32Type: + enc.AddUint32(f.Key, uint32(f.Integer)) + case Uint16Type: + enc.AddUint16(f.Key, uint16(f.Integer)) + case Uint8Type: + enc.AddUint8(f.Key, uint8(f.Integer)) + case UintptrType: + enc.AddUintptr(f.Key, uintptr(f.Integer)) + case ReflectType: + err = enc.AddReflected(f.Key, f.Interface) + case NamespaceType: + enc.OpenNamespace(f.Key) + case StringerType: + err = encodeStringer(f.Key, f.Interface, enc) + case ErrorType: + err = encodeError(f.Key, f.Interface.(error), enc) + case SkipType: + break + default: + panic(fmt.Sprintf("unknown field type: %v", f)) + } + + if err != nil { + enc.AddString(fmt.Sprintf("%sError", f.Key), err.Error()) + } +} + +// Equals returns whether two fields are equal. For non-primitive types such as +// errors, marshalers, or reflect types, it uses reflect.DeepEqual. +func (f Field) Equals(other Field) bool { + if f.Type != other.Type { + return false + } + if f.Key != other.Key { + return false + } + + switch f.Type { + case BinaryType, ByteStringType: + return bytes.Equal(f.Interface.([]byte), other.Interface.([]byte)) + case ArrayMarshalerType, ObjectMarshalerType, ErrorType, ReflectType: + return reflect.DeepEqual(f.Interface, other.Interface) + default: + return f == other + } +} + +func addFields(enc ObjectEncoder, fields []Field) { + for i := range fields { + fields[i].AddTo(enc) + } +} + +func encodeStringer(key string, stringer interface{}, enc ObjectEncoder) (retErr error) { + // Try to capture panics (from nil references or otherwise) when calling + // the String() method, similar to https://golang.org/src/fmt/print.go#L540 + defer func() { + if err := recover(); err != nil { + // If it's a nil pointer, just say "<nil>". The likeliest causes are a + // Stringer that fails to guard against nil or a nil pointer for a + // value receiver, and in either case, "<nil>" is a nice result. + if v := reflect.ValueOf(stringer); v.Kind() == reflect.Ptr && v.IsNil() { + enc.AddString(key, "<nil>") + return + } + + retErr = fmt.Errorf("PANIC=%v", err) + } + }() + + enc.AddString(key, stringer.(fmt.Stringer).String()) + return nil +} diff --git a/vendor/go.uber.org/zap/zapcore/gotest/ya.make b/vendor/go.uber.org/zap/zapcore/gotest/ya.make new file mode 100644 index 0000000000..3f45ec92e2 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/gotest/ya.make @@ -0,0 +1,5 @@ +GO_TEST_FOR(vendor/go.uber.org/zap/zapcore) + +LICENSE(MIT) + +END() diff --git a/vendor/go.uber.org/zap/zapcore/hook.go b/vendor/go.uber.org/zap/zapcore/hook.go new file mode 100644 index 0000000000..198def9917 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/hook.go @@ -0,0 +1,77 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "go.uber.org/multierr" + +type hooked struct { + Core + funcs []func(Entry) error +} + +var ( + _ Core = (*hooked)(nil) + _ leveledEnabler = (*hooked)(nil) +) + +// RegisterHooks wraps a Core and runs a collection of user-defined callback +// hooks each time a message is logged. Execution of the callbacks is blocking. +// +// This offers users an easy way to register simple callbacks (e.g., metrics +// collection) without implementing the full Core interface. +func RegisterHooks(core Core, hooks ...func(Entry) error) Core { + funcs := append([]func(Entry) error{}, hooks...) + return &hooked{ + Core: core, + funcs: funcs, + } +} + +func (h *hooked) Level() Level { + return LevelOf(h.Core) +} + +func (h *hooked) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + // Let the wrapped Core decide whether to log this message or not. This + // also gives the downstream a chance to register itself directly with the + // CheckedEntry. + if downstream := h.Core.Check(ent, ce); downstream != nil { + return downstream.AddCore(ent, h) + } + return ce +} + +func (h *hooked) With(fields []Field) Core { + return &hooked{ + Core: h.Core.With(fields), + funcs: h.funcs, + } +} + +func (h *hooked) Write(ent Entry, _ []Field) error { + // Since our downstream had a chance to register itself directly with the + // CheckedMessage, we don't need to call it here. + var err error + for i := range h.funcs { + err = multierr.Append(err, h.funcs[i](ent)) + } + return err +} diff --git a/vendor/go.uber.org/zap/zapcore/increase_level.go b/vendor/go.uber.org/zap/zapcore/increase_level.go new file mode 100644 index 0000000000..7a11237ae9 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/increase_level.go @@ -0,0 +1,75 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "fmt" + +type levelFilterCore struct { + core Core + level LevelEnabler +} + +var ( + _ Core = (*levelFilterCore)(nil) + _ leveledEnabler = (*levelFilterCore)(nil) +) + +// NewIncreaseLevelCore creates a core that can be used to increase the level of +// an existing Core. It cannot be used to decrease the logging level, as it acts +// as a filter before calling the underlying core. If level decreases the log level, +// an error is returned. +func NewIncreaseLevelCore(core Core, level LevelEnabler) (Core, error) { + for l := _maxLevel; l >= _minLevel; l-- { + if !core.Enabled(l) && level.Enabled(l) { + return nil, fmt.Errorf("invalid increase level, as level %q is allowed by increased level, but not by existing core", l) + } + } + + return &levelFilterCore{core, level}, nil +} + +func (c *levelFilterCore) Enabled(lvl Level) bool { + return c.level.Enabled(lvl) +} + +func (c *levelFilterCore) Level() Level { + return LevelOf(c.level) +} + +func (c *levelFilterCore) With(fields []Field) Core { + return &levelFilterCore{c.core.With(fields), c.level} +} + +func (c *levelFilterCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + if !c.Enabled(ent.Level) { + return ce + } + + return c.core.Check(ent, ce) +} + +func (c *levelFilterCore) Write(ent Entry, fields []Field) error { + return c.core.Write(ent, fields) +} + +func (c *levelFilterCore) Sync() error { + return c.core.Sync() +} diff --git a/vendor/go.uber.org/zap/zapcore/json_encoder.go b/vendor/go.uber.org/zap/zapcore/json_encoder.go new file mode 100644 index 0000000000..c8ab86979b --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/json_encoder.go @@ -0,0 +1,583 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "encoding/base64" + "math" + "time" + "unicode/utf8" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/internal/bufferpool" + "go.uber.org/zap/internal/pool" +) + +// For JSON-escaping; see jsonEncoder.safeAddString below. +const _hex = "0123456789abcdef" + +var _jsonPool = pool.New(func() *jsonEncoder { + return &jsonEncoder{} +}) + +func putJSONEncoder(enc *jsonEncoder) { + if enc.reflectBuf != nil { + enc.reflectBuf.Free() + } + enc.EncoderConfig = nil + enc.buf = nil + enc.spaced = false + enc.openNamespaces = 0 + enc.reflectBuf = nil + enc.reflectEnc = nil + _jsonPool.Put(enc) +} + +type jsonEncoder struct { + *EncoderConfig + buf *buffer.Buffer + spaced bool // include spaces after colons and commas + openNamespaces int + + // for encoding generic values by reflection + reflectBuf *buffer.Buffer + reflectEnc ReflectedEncoder +} + +// NewJSONEncoder creates a fast, low-allocation JSON encoder. The encoder +// appropriately escapes all field keys and values. +// +// Note that the encoder doesn't deduplicate keys, so it's possible to produce +// a message like +// +// {"foo":"bar","foo":"baz"} +// +// This is permitted by the JSON specification, but not encouraged. Many +// libraries will ignore duplicate key-value pairs (typically keeping the last +// pair) when unmarshaling, but users should attempt to avoid adding duplicate +// keys. +func NewJSONEncoder(cfg EncoderConfig) Encoder { + return newJSONEncoder(cfg, false) +} + +func newJSONEncoder(cfg EncoderConfig, spaced bool) *jsonEncoder { + if cfg.SkipLineEnding { + cfg.LineEnding = "" + } else if cfg.LineEnding == "" { + cfg.LineEnding = DefaultLineEnding + } + + // If no EncoderConfig.NewReflectedEncoder is provided by the user, then use default + if cfg.NewReflectedEncoder == nil { + cfg.NewReflectedEncoder = defaultReflectedEncoder + } + + return &jsonEncoder{ + EncoderConfig: &cfg, + buf: bufferpool.Get(), + spaced: spaced, + } +} + +func (enc *jsonEncoder) AddArray(key string, arr ArrayMarshaler) error { + enc.addKey(key) + return enc.AppendArray(arr) +} + +func (enc *jsonEncoder) AddObject(key string, obj ObjectMarshaler) error { + enc.addKey(key) + return enc.AppendObject(obj) +} + +func (enc *jsonEncoder) AddBinary(key string, val []byte) { + enc.AddString(key, base64.StdEncoding.EncodeToString(val)) +} + +func (enc *jsonEncoder) AddByteString(key string, val []byte) { + enc.addKey(key) + enc.AppendByteString(val) +} + +func (enc *jsonEncoder) AddBool(key string, val bool) { + enc.addKey(key) + enc.AppendBool(val) +} + +func (enc *jsonEncoder) AddComplex128(key string, val complex128) { + enc.addKey(key) + enc.AppendComplex128(val) +} + +func (enc *jsonEncoder) AddComplex64(key string, val complex64) { + enc.addKey(key) + enc.AppendComplex64(val) +} + +func (enc *jsonEncoder) AddDuration(key string, val time.Duration) { + enc.addKey(key) + enc.AppendDuration(val) +} + +func (enc *jsonEncoder) AddFloat64(key string, val float64) { + enc.addKey(key) + enc.AppendFloat64(val) +} + +func (enc *jsonEncoder) AddFloat32(key string, val float32) { + enc.addKey(key) + enc.AppendFloat32(val) +} + +func (enc *jsonEncoder) AddInt64(key string, val int64) { + enc.addKey(key) + enc.AppendInt64(val) +} + +func (enc *jsonEncoder) resetReflectBuf() { + if enc.reflectBuf == nil { + enc.reflectBuf = bufferpool.Get() + enc.reflectEnc = enc.NewReflectedEncoder(enc.reflectBuf) + } else { + enc.reflectBuf.Reset() + } +} + +var nullLiteralBytes = []byte("null") + +// Only invoke the standard JSON encoder if there is actually something to +// encode; otherwise write JSON null literal directly. +func (enc *jsonEncoder) encodeReflected(obj interface{}) ([]byte, error) { + if obj == nil { + return nullLiteralBytes, nil + } + enc.resetReflectBuf() + if err := enc.reflectEnc.Encode(obj); err != nil { + return nil, err + } + enc.reflectBuf.TrimNewline() + return enc.reflectBuf.Bytes(), nil +} + +func (enc *jsonEncoder) AddReflected(key string, obj interface{}) error { + valueBytes, err := enc.encodeReflected(obj) + if err != nil { + return err + } + enc.addKey(key) + _, err = enc.buf.Write(valueBytes) + return err +} + +func (enc *jsonEncoder) OpenNamespace(key string) { + enc.addKey(key) + enc.buf.AppendByte('{') + enc.openNamespaces++ +} + +func (enc *jsonEncoder) AddString(key, val string) { + enc.addKey(key) + enc.AppendString(val) +} + +func (enc *jsonEncoder) AddTime(key string, val time.Time) { + enc.addKey(key) + enc.AppendTime(val) +} + +func (enc *jsonEncoder) AddUint64(key string, val uint64) { + enc.addKey(key) + enc.AppendUint64(val) +} + +func (enc *jsonEncoder) AppendArray(arr ArrayMarshaler) error { + enc.addElementSeparator() + enc.buf.AppendByte('[') + err := arr.MarshalLogArray(enc) + enc.buf.AppendByte(']') + return err +} + +func (enc *jsonEncoder) AppendObject(obj ObjectMarshaler) error { + // Close ONLY new openNamespaces that are created during + // AppendObject(). + old := enc.openNamespaces + enc.openNamespaces = 0 + enc.addElementSeparator() + enc.buf.AppendByte('{') + err := obj.MarshalLogObject(enc) + enc.buf.AppendByte('}') + enc.closeOpenNamespaces() + enc.openNamespaces = old + return err +} + +func (enc *jsonEncoder) AppendBool(val bool) { + enc.addElementSeparator() + enc.buf.AppendBool(val) +} + +func (enc *jsonEncoder) AppendByteString(val []byte) { + enc.addElementSeparator() + enc.buf.AppendByte('"') + enc.safeAddByteString(val) + enc.buf.AppendByte('"') +} + +// appendComplex appends the encoded form of the provided complex128 value. +// precision specifies the encoding precision for the real and imaginary +// components of the complex number. +func (enc *jsonEncoder) appendComplex(val complex128, precision int) { + enc.addElementSeparator() + // Cast to a platform-independent, fixed-size type. + r, i := float64(real(val)), float64(imag(val)) + enc.buf.AppendByte('"') + // Because we're always in a quoted string, we can use strconv without + // special-casing NaN and +/-Inf. + enc.buf.AppendFloat(r, precision) + // If imaginary part is less than 0, minus (-) sign is added by default + // by AppendFloat. + if i >= 0 { + enc.buf.AppendByte('+') + } + enc.buf.AppendFloat(i, precision) + enc.buf.AppendByte('i') + enc.buf.AppendByte('"') +} + +func (enc *jsonEncoder) AppendDuration(val time.Duration) { + cur := enc.buf.Len() + if e := enc.EncodeDuration; e != nil { + e(val, enc) + } + if cur == enc.buf.Len() { + // User-supplied EncodeDuration is a no-op. Fall back to nanoseconds to keep + // JSON valid. + enc.AppendInt64(int64(val)) + } +} + +func (enc *jsonEncoder) AppendInt64(val int64) { + enc.addElementSeparator() + enc.buf.AppendInt(val) +} + +func (enc *jsonEncoder) AppendReflected(val interface{}) error { + valueBytes, err := enc.encodeReflected(val) + if err != nil { + return err + } + enc.addElementSeparator() + _, err = enc.buf.Write(valueBytes) + return err +} + +func (enc *jsonEncoder) AppendString(val string) { + enc.addElementSeparator() + enc.buf.AppendByte('"') + enc.safeAddString(val) + enc.buf.AppendByte('"') +} + +func (enc *jsonEncoder) AppendTimeLayout(time time.Time, layout string) { + enc.addElementSeparator() + enc.buf.AppendByte('"') + enc.buf.AppendTime(time, layout) + enc.buf.AppendByte('"') +} + +func (enc *jsonEncoder) AppendTime(val time.Time) { + cur := enc.buf.Len() + if e := enc.EncodeTime; e != nil { + e(val, enc) + } + if cur == enc.buf.Len() { + // User-supplied EncodeTime is a no-op. Fall back to nanos since epoch to keep + // output JSON valid. + enc.AppendInt64(val.UnixNano()) + } +} + +func (enc *jsonEncoder) AppendUint64(val uint64) { + enc.addElementSeparator() + enc.buf.AppendUint(val) +} + +func (enc *jsonEncoder) AddInt(k string, v int) { enc.AddInt64(k, int64(v)) } +func (enc *jsonEncoder) AddInt32(k string, v int32) { enc.AddInt64(k, int64(v)) } +func (enc *jsonEncoder) AddInt16(k string, v int16) { enc.AddInt64(k, int64(v)) } +func (enc *jsonEncoder) AddInt8(k string, v int8) { enc.AddInt64(k, int64(v)) } +func (enc *jsonEncoder) AddUint(k string, v uint) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AddUint32(k string, v uint32) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AddUint16(k string, v uint16) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AddUint8(k string, v uint8) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AddUintptr(k string, v uintptr) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AppendComplex64(v complex64) { enc.appendComplex(complex128(v), 32) } +func (enc *jsonEncoder) AppendComplex128(v complex128) { enc.appendComplex(complex128(v), 64) } +func (enc *jsonEncoder) AppendFloat64(v float64) { enc.appendFloat(v, 64) } +func (enc *jsonEncoder) AppendFloat32(v float32) { enc.appendFloat(float64(v), 32) } +func (enc *jsonEncoder) AppendInt(v int) { enc.AppendInt64(int64(v)) } +func (enc *jsonEncoder) AppendInt32(v int32) { enc.AppendInt64(int64(v)) } +func (enc *jsonEncoder) AppendInt16(v int16) { enc.AppendInt64(int64(v)) } +func (enc *jsonEncoder) AppendInt8(v int8) { enc.AppendInt64(int64(v)) } +func (enc *jsonEncoder) AppendUint(v uint) { enc.AppendUint64(uint64(v)) } +func (enc *jsonEncoder) AppendUint32(v uint32) { enc.AppendUint64(uint64(v)) } +func (enc *jsonEncoder) AppendUint16(v uint16) { enc.AppendUint64(uint64(v)) } +func (enc *jsonEncoder) AppendUint8(v uint8) { enc.AppendUint64(uint64(v)) } +func (enc *jsonEncoder) AppendUintptr(v uintptr) { enc.AppendUint64(uint64(v)) } + +func (enc *jsonEncoder) Clone() Encoder { + clone := enc.clone() + clone.buf.Write(enc.buf.Bytes()) + return clone +} + +func (enc *jsonEncoder) clone() *jsonEncoder { + clone := _jsonPool.Get() + clone.EncoderConfig = enc.EncoderConfig + clone.spaced = enc.spaced + clone.openNamespaces = enc.openNamespaces + clone.buf = bufferpool.Get() + return clone +} + +func (enc *jsonEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, error) { + final := enc.clone() + final.buf.AppendByte('{') + + if final.LevelKey != "" && final.EncodeLevel != nil { + final.addKey(final.LevelKey) + cur := final.buf.Len() + final.EncodeLevel(ent.Level, final) + if cur == final.buf.Len() { + // User-supplied EncodeLevel was a no-op. Fall back to strings to keep + // output JSON valid. + final.AppendString(ent.Level.String()) + } + } + if final.TimeKey != "" { + final.AddTime(final.TimeKey, ent.Time) + } + if ent.LoggerName != "" && final.NameKey != "" { + final.addKey(final.NameKey) + cur := final.buf.Len() + nameEncoder := final.EncodeName + + // if no name encoder provided, fall back to FullNameEncoder for backwards + // compatibility + if nameEncoder == nil { + nameEncoder = FullNameEncoder + } + + nameEncoder(ent.LoggerName, final) + if cur == final.buf.Len() { + // User-supplied EncodeName was a no-op. Fall back to strings to + // keep output JSON valid. + final.AppendString(ent.LoggerName) + } + } + if ent.Caller.Defined { + if final.CallerKey != "" { + final.addKey(final.CallerKey) + cur := final.buf.Len() + final.EncodeCaller(ent.Caller, final) + if cur == final.buf.Len() { + // User-supplied EncodeCaller was a no-op. Fall back to strings to + // keep output JSON valid. + final.AppendString(ent.Caller.String()) + } + } + if final.FunctionKey != "" { + final.addKey(final.FunctionKey) + final.AppendString(ent.Caller.Function) + } + } + if final.MessageKey != "" { + final.addKey(enc.MessageKey) + final.AppendString(ent.Message) + } + if enc.buf.Len() > 0 { + final.addElementSeparator() + final.buf.Write(enc.buf.Bytes()) + } + addFields(final, fields) + final.closeOpenNamespaces() + if ent.Stack != "" && final.StacktraceKey != "" { + final.AddString(final.StacktraceKey, ent.Stack) + } + final.buf.AppendByte('}') + final.buf.AppendString(final.LineEnding) + + ret := final.buf + putJSONEncoder(final) + return ret, nil +} + +func (enc *jsonEncoder) truncate() { + enc.buf.Reset() +} + +func (enc *jsonEncoder) closeOpenNamespaces() { + for i := 0; i < enc.openNamespaces; i++ { + enc.buf.AppendByte('}') + } + enc.openNamespaces = 0 +} + +func (enc *jsonEncoder) addKey(key string) { + enc.addElementSeparator() + enc.buf.AppendByte('"') + enc.safeAddString(key) + enc.buf.AppendByte('"') + enc.buf.AppendByte(':') + if enc.spaced { + enc.buf.AppendByte(' ') + } +} + +func (enc *jsonEncoder) addElementSeparator() { + last := enc.buf.Len() - 1 + if last < 0 { + return + } + switch enc.buf.Bytes()[last] { + case '{', '[', ':', ',', ' ': + return + default: + enc.buf.AppendByte(',') + if enc.spaced { + enc.buf.AppendByte(' ') + } + } +} + +func (enc *jsonEncoder) appendFloat(val float64, bitSize int) { + enc.addElementSeparator() + switch { + case math.IsNaN(val): + enc.buf.AppendString(`"NaN"`) + case math.IsInf(val, 1): + enc.buf.AppendString(`"+Inf"`) + case math.IsInf(val, -1): + enc.buf.AppendString(`"-Inf"`) + default: + enc.buf.AppendFloat(val, bitSize) + } +} + +// safeAddString JSON-escapes a string and appends it to the internal buffer. +// Unlike the standard library's encoder, it doesn't attempt to protect the +// user from browser vulnerabilities or JSONP-related problems. +func (enc *jsonEncoder) safeAddString(s string) { + safeAppendStringLike( + (*buffer.Buffer).AppendString, + utf8.DecodeRuneInString, + enc.buf, + s, + ) +} + +// safeAddByteString is no-alloc equivalent of safeAddString(string(s)) for s []byte. +func (enc *jsonEncoder) safeAddByteString(s []byte) { + safeAppendStringLike( + (*buffer.Buffer).AppendBytes, + utf8.DecodeRune, + enc.buf, + s, + ) +} + +// safeAppendStringLike is a generic implementation of safeAddString and safeAddByteString. +// It appends a string or byte slice to the buffer, escaping all special characters. +func safeAppendStringLike[S []byte | string]( + // appendTo appends this string-like object to the buffer. + appendTo func(*buffer.Buffer, S), + // decodeRune decodes the next rune from the string-like object + // and returns its value and width in bytes. + decodeRune func(S) (rune, int), + buf *buffer.Buffer, + s S, +) { + // The encoding logic below works by skipping over characters + // that can be safely copied as-is, + // until a character is found that needs special handling. + // At that point, we copy everything we've seen so far, + // and then handle that special character. + // + // last is the index of the last byte that was copied to the buffer. + last := 0 + for i := 0; i < len(s); { + if s[i] >= utf8.RuneSelf { + // Character >= RuneSelf may be part of a multi-byte rune. + // They need to be decoded before we can decide how to handle them. + r, size := decodeRune(s[i:]) + if r != utf8.RuneError || size != 1 { + // No special handling required. + // Skip over this rune and continue. + i += size + continue + } + + // Invalid UTF-8 sequence. + // Replace it with the Unicode replacement character. + appendTo(buf, s[last:i]) + buf.AppendString(`\ufffd`) + + i++ + last = i + } else { + // Character < RuneSelf is a single-byte UTF-8 rune. + if s[i] >= 0x20 && s[i] != '\\' && s[i] != '"' { + // No escaping necessary. + // Skip over this character and continue. + i++ + continue + } + + // This character needs to be escaped. + appendTo(buf, s[last:i]) + switch s[i] { + case '\\', '"': + buf.AppendByte('\\') + buf.AppendByte(s[i]) + case '\n': + buf.AppendByte('\\') + buf.AppendByte('n') + case '\r': + buf.AppendByte('\\') + buf.AppendByte('r') + case '\t': + buf.AppendByte('\\') + buf.AppendByte('t') + default: + // Encode bytes < 0x20, except for the escape sequences above. + buf.AppendString(`\u00`) + buf.AppendByte(_hex[s[i]>>4]) + buf.AppendByte(_hex[s[i]&0xF]) + } + + i++ + last = i + } + } + + // add remaining + appendTo(buf, s[last:]) +} diff --git a/vendor/go.uber.org/zap/zapcore/json_encoder_bench_test.go b/vendor/go.uber.org/zap/zapcore/json_encoder_bench_test.go new file mode 100644 index 0000000000..9182b39518 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/json_encoder_bench_test.go @@ -0,0 +1,131 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "encoding/json" + "fmt" + "testing" + "time" + + . "go.uber.org/zap/zapcore" +) + +func BenchmarkJSONLogMarshalerFunc(b *testing.B) { + for i := 0; i < b.N; i++ { + enc := NewJSONEncoder(testEncoderConfig()) + err := enc.AddObject("nested", ObjectMarshalerFunc(func(enc ObjectEncoder) error { + enc.AddInt64("i", int64(i)) + return nil + })) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkZapJSONFloat32AndComplex64(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + enc := NewJSONEncoder(testEncoderConfig()) + enc.AddFloat32("float32", 3.14) + enc.AddComplex64("complex64", 2.71+3.14i) + } + }) +} + +const _sliceSize = 5000 + +type StringSlice []string + +func (s StringSlice) MarshalLogArray(encoder ArrayEncoder) error { + for _, str := range s { + encoder.AppendString(str) + } + return nil +} + +func generateStringSlice(n int) StringSlice { + output := make(StringSlice, 0, n) + for i := 0; i < n; i++ { + output = append(output, fmt.Sprint("00000000-0000-0000-0000-0000000000", i)) + } + return output +} + +func BenchmarkZapJSON(b *testing.B) { + additional := generateStringSlice(_sliceSize) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + enc := NewJSONEncoder(testEncoderConfig()) + enc.AddString("str", "foo") + enc.AddInt64("int64-1", 1) + enc.AddInt64("int64-2", 2) + enc.AddFloat64("float64", 1.0) + enc.AddString("string1", "\n") + enc.AddString("string2", "💩") + enc.AddString("string3", "🤔") + enc.AddString("string4", "🙊") + enc.AddBool("bool", true) + _ = enc.AddArray("test", additional) + buf, _ := enc.EncodeEntry(Entry{ + Message: "fake", + Level: DebugLevel, + }, nil) + buf.Free() + } + }) +} + +func BenchmarkStandardJSON(b *testing.B) { + record := struct { + Level string `json:"level"` + Message string `json:"msg"` + Time time.Time `json:"ts"` + Fields map[string]interface{} `json:"fields"` + Additional StringSlice + }{ + Level: "debug", + Message: "fake", + Time: time.Unix(0, 0), + Fields: map[string]interface{}{ + "str": "foo", + "int64-1": int64(1), + "int64-2": int64(1), + "float64": float64(1.0), + "string1": "\n", + "string2": "💩", + "string3": "🤔", + "string4": "🙊", + "bool": true, + }, + Additional: generateStringSlice(_sliceSize), + } + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := json.Marshal(record); err != nil { + b.Fatal(err) + } + } + }) +} diff --git a/vendor/go.uber.org/zap/zapcore/json_encoder_impl_test.go b/vendor/go.uber.org/zap/zapcore/json_encoder_impl_test.go new file mode 100644 index 0000000000..5f81262751 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/json_encoder_impl_test.go @@ -0,0 +1,736 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "encoding/json" + "errors" + "math" + "math/rand" + "reflect" + "testing" + "testing/quick" + "time" + "unicode/utf8" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/internal/bufferpool" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/multierr" +) + +var _defaultEncoderConfig = EncoderConfig{ + EncodeTime: EpochTimeEncoder, + EncodeDuration: SecondsDurationEncoder, +} + +func TestJSONClone(t *testing.T) { + // The parent encoder is created with plenty of excess capacity. + parent := &jsonEncoder{buf: bufferpool.Get()} + clone := parent.Clone() + + // Adding to the parent shouldn't affect the clone, and vice versa. + parent.AddString("foo", "bar") + clone.AddString("baz", "bing") + + assertJSON(t, `"foo":"bar"`, parent) + assertJSON(t, `"baz":"bing"`, clone.(*jsonEncoder)) +} + +func TestJSONEscaping(t *testing.T) { + enc := &jsonEncoder{buf: bufferpool.Get()} + // Test all the edge cases of JSON escaping directly. + cases := map[string]string{ + // ASCII. + `foo`: `foo`, + // Special-cased characters. + `"`: `\"`, + `\`: `\\`, + // Special-cased characters within everyday ASCII. + `foo"foo`: `foo\"foo`, + "foo\n": `foo\n`, + // Special-cased control characters. + "\n": `\n`, + "\r": `\r`, + "\t": `\t`, + // \b and \f are sometimes backslash-escaped, but this representation is also + // conformant. + "\b": `\u0008`, + "\f": `\u000c`, + // The standard lib special-cases angle brackets and ampersands by default, + // because it wants to protect users from browser exploits. In a logging + // context, we shouldn't special-case these characters. + "<": "<", + ">": ">", + "&": "&", + // ASCII bell - not special-cased. + string(byte(0x07)): `\u0007`, + // Astral-plane unicode. + `☃`: `☃`, + // Decodes to (RuneError, 1) + "\xed\xa0\x80": `\ufffd\ufffd\ufffd`, + "foo\xed\xa0\x80": `foo\ufffd\ufffd\ufffd`, + } + + t.Run("String", func(t *testing.T) { + for input, output := range cases { + enc.truncate() + enc.safeAddString(input) + assertJSON(t, output, enc) + } + }) + + t.Run("ByteString", func(t *testing.T) { + for input, output := range cases { + enc.truncate() + enc.safeAddByteString([]byte(input)) + assertJSON(t, output, enc) + } + }) +} + +func TestJSONEncoderObjectFields(t *testing.T) { + tests := []struct { + desc string + expected string + f func(Encoder) + }{ + {"binary", `"k":"YWIxMg=="`, func(e Encoder) { e.AddBinary("k", []byte("ab12")) }}, + {"bool", `"k\\":true`, func(e Encoder) { e.AddBool(`k\`, true) }}, // test key escaping once + {"bool", `"k":true`, func(e Encoder) { e.AddBool("k", true) }}, + {"bool", `"k":false`, func(e Encoder) { e.AddBool("k", false) }}, + {"byteString", `"k":"v\\"`, func(e Encoder) { e.AddByteString(`k`, []byte(`v\`)) }}, + {"byteString", `"k":"v"`, func(e Encoder) { e.AddByteString("k", []byte("v")) }}, + {"byteString", `"k":""`, func(e Encoder) { e.AddByteString("k", []byte{}) }}, + {"byteString", `"k":""`, func(e Encoder) { e.AddByteString("k", nil) }}, + {"complex128", `"k":"1+2i"`, func(e Encoder) { e.AddComplex128("k", 1+2i) }}, + {"complex128/negative_i", `"k":"1-2i"`, func(e Encoder) { e.AddComplex128("k", 1-2i) }}, + {"complex64", `"k":"1+2i"`, func(e Encoder) { e.AddComplex64("k", 1+2i) }}, + {"complex64/negative_i", `"k":"1-2i"`, func(e Encoder) { e.AddComplex64("k", 1-2i) }}, + {"complex64", `"k":"2.71+3.14i"`, func(e Encoder) { e.AddComplex64("k", 2.71+3.14i) }}, + {"duration", `"k":0.000000001`, func(e Encoder) { e.AddDuration("k", 1) }}, + {"duration/negative", `"k":-0.000000001`, func(e Encoder) { e.AddDuration("k", -1) }}, + {"float64", `"k":1`, func(e Encoder) { e.AddFloat64("k", 1.0) }}, + {"float64", `"k":10000000000`, func(e Encoder) { e.AddFloat64("k", 1e10) }}, + {"float64", `"k":"NaN"`, func(e Encoder) { e.AddFloat64("k", math.NaN()) }}, + {"float64", `"k":"+Inf"`, func(e Encoder) { e.AddFloat64("k", math.Inf(1)) }}, + {"float64", `"k":"-Inf"`, func(e Encoder) { e.AddFloat64("k", math.Inf(-1)) }}, + {"float64/pi", `"k":3.141592653589793`, func(e Encoder) { e.AddFloat64("k", math.Pi) }}, + {"float32", `"k":1`, func(e Encoder) { e.AddFloat32("k", 1.0) }}, + {"float32", `"k":2.71`, func(e Encoder) { e.AddFloat32("k", 2.71) }}, + {"float32", `"k":0.1`, func(e Encoder) { e.AddFloat32("k", 0.1) }}, + {"float32", `"k":10000000000`, func(e Encoder) { e.AddFloat32("k", 1e10) }}, + {"float32", `"k":"NaN"`, func(e Encoder) { e.AddFloat32("k", float32(math.NaN())) }}, + {"float32", `"k":"+Inf"`, func(e Encoder) { e.AddFloat32("k", float32(math.Inf(1))) }}, + {"float32", `"k":"-Inf"`, func(e Encoder) { e.AddFloat32("k", float32(math.Inf(-1))) }}, + {"float32/pi", `"k":3.1415927`, func(e Encoder) { e.AddFloat32("k", math.Pi) }}, + {"int", `"k":42`, func(e Encoder) { e.AddInt("k", 42) }}, + {"int64", `"k":42`, func(e Encoder) { e.AddInt64("k", 42) }}, + {"int64/min", `"k":-9223372036854775808`, func(e Encoder) { e.AddInt64("k", math.MinInt64) }}, + {"int64/max", `"k":9223372036854775807`, func(e Encoder) { e.AddInt64("k", math.MaxInt64) }}, + {"int32", `"k":42`, func(e Encoder) { e.AddInt32("k", 42) }}, + {"int32/min", `"k":-2147483648`, func(e Encoder) { e.AddInt32("k", math.MinInt32) }}, + {"int32/max", `"k":2147483647`, func(e Encoder) { e.AddInt32("k", math.MaxInt32) }}, + {"int16", `"k":42`, func(e Encoder) { e.AddInt16("k", 42) }}, + {"int16/min", `"k":-32768`, func(e Encoder) { e.AddInt16("k", math.MinInt16) }}, + {"int16/max", `"k":32767`, func(e Encoder) { e.AddInt16("k", math.MaxInt16) }}, + {"int8", `"k":42`, func(e Encoder) { e.AddInt8("k", 42) }}, + {"int8/min", `"k":-128`, func(e Encoder) { e.AddInt8("k", math.MinInt8) }}, + {"int8/max", `"k":127`, func(e Encoder) { e.AddInt8("k", math.MaxInt8) }}, + {"string", `"k":"v\\"`, func(e Encoder) { e.AddString(`k`, `v\`) }}, + {"string", `"k":"v"`, func(e Encoder) { e.AddString("k", "v") }}, + {"string", `"k":""`, func(e Encoder) { e.AddString("k", "") }}, + {"time", `"k":1`, func(e Encoder) { e.AddTime("k", time.Unix(1, 0)) }}, + {"uint", `"k":42`, func(e Encoder) { e.AddUint("k", 42) }}, + {"uint64", `"k":42`, func(e Encoder) { e.AddUint64("k", 42) }}, + {"uint64/max", `"k":18446744073709551615`, func(e Encoder) { e.AddUint64("k", math.MaxUint64) }}, + {"uint32", `"k":42`, func(e Encoder) { e.AddUint32("k", 42) }}, + {"uint32/max", `"k":4294967295`, func(e Encoder) { e.AddUint32("k", math.MaxUint32) }}, + {"uint16", `"k":42`, func(e Encoder) { e.AddUint16("k", 42) }}, + {"uint16/max", `"k":65535`, func(e Encoder) { e.AddUint16("k", math.MaxUint16) }}, + {"uint8", `"k":42`, func(e Encoder) { e.AddUint8("k", 42) }}, + {"uint8/max", `"k":255`, func(e Encoder) { e.AddUint8("k", math.MaxUint8) }}, + {"uintptr", `"k":42`, func(e Encoder) { e.AddUintptr("k", 42) }}, + { + desc: "object (success)", + expected: `"k":{"loggable":"yes"}`, + f: func(e Encoder) { + assert.NoError(t, e.AddObject("k", loggable{true}), "Unexpected error calling MarshalLogObject.") + }, + }, + { + desc: "object (error)", + expected: `"k":{}`, + f: func(e Encoder) { + assert.Error(t, e.AddObject("k", loggable{false}), "Expected an error calling MarshalLogObject.") + }, + }, + { + desc: "object (with nested array)", + expected: `"turducken":{"ducks":[{"in":"chicken"},{"in":"chicken"}]}`, + f: func(e Encoder) { + assert.NoError( + t, + e.AddObject("turducken", turducken{}), + "Unexpected error calling MarshalLogObject with nested ObjectMarshalers and ArrayMarshalers.", + ) + }, + }, + { + desc: "array (with nested object)", + expected: `"turduckens":[{"ducks":[{"in":"chicken"},{"in":"chicken"}]},{"ducks":[{"in":"chicken"},{"in":"chicken"}]}]`, + f: func(e Encoder) { + assert.NoError( + t, + e.AddArray("turduckens", turduckens(2)), + "Unexpected error calling MarshalLogObject with nested ObjectMarshalers and ArrayMarshalers.", + ) + }, + }, + { + desc: "array (success)", + expected: `"k":[true]`, + f: func(e Encoder) { + assert.NoError(t, e.AddArray(`k`, loggable{true}), "Unexpected error calling MarshalLogArray.") + }, + }, + { + desc: "array (error)", + expected: `"k":[]`, + f: func(e Encoder) { + assert.Error(t, e.AddArray("k", loggable{false}), "Expected an error calling MarshalLogArray.") + }, + }, + { + desc: "reflect (success)", + expected: `"k":{"escape":"<&>","loggable":"yes"}`, + f: func(e Encoder) { + assert.NoError(t, e.AddReflected("k", map[string]string{"escape": "<&>", "loggable": "yes"}), "Unexpected error JSON-serializing a map.") + }, + }, + { + desc: "reflect (failure)", + expected: "", + f: func(e Encoder) { + assert.Error(t, e.AddReflected("k", noJSON{}), "Unexpected success JSON-serializing a noJSON.") + }, + }, + { + desc: "namespace", + // EncodeEntry is responsible for closing all open namespaces. + expected: `"outermost":{"outer":{"foo":1,"inner":{"foo":2,"innermost":{`, + f: func(e Encoder) { + e.OpenNamespace("outermost") + e.OpenNamespace("outer") + e.AddInt("foo", 1) + e.OpenNamespace("inner") + e.AddInt("foo", 2) + e.OpenNamespace("innermost") + }, + }, + { + desc: "object (no nested namespace)", + expected: `"obj":{"obj-out":"obj-outside-namespace"},"not-obj":"should-be-outside-obj"`, + f: func(e Encoder) { + assert.NoError(t, e.AddObject("obj", maybeNamespace{false})) + e.AddString("not-obj", "should-be-outside-obj") + }, + }, + { + desc: "object (with nested namespace)", + expected: `"obj":{"obj-out":"obj-outside-namespace","obj-namespace":{"obj-in":"obj-inside-namespace"}},"not-obj":"should-be-outside-obj"`, + f: func(e Encoder) { + assert.NoError(t, e.AddObject("obj", maybeNamespace{true})) + e.AddString("not-obj", "should-be-outside-obj") + }, + }, + { + desc: "multiple open namespaces", + expected: `"k":{"foo":1,"middle":{"foo":2,"inner":{"foo":3}}}`, + f: func(e Encoder) { + err := e.AddObject("k", ObjectMarshalerFunc(func(enc ObjectEncoder) error { + e.AddInt("foo", 1) + e.OpenNamespace("middle") + e.AddInt("foo", 2) + e.OpenNamespace("inner") + e.AddInt("foo", 3) + return nil + })) + assert.NoError(t, err) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + assertOutput(t, _defaultEncoderConfig, tt.expected, tt.f) + }) + } +} + +func TestJSONEncoderTimeFormats(t *testing.T) { + date := time.Date(2000, time.January, 2, 3, 4, 5, 6, time.UTC) + + f := func(e Encoder) { + e.AddTime("k", date) + err := e.AddArray("a", ArrayMarshalerFunc(func(enc ArrayEncoder) error { + enc.AppendTime(date) + return nil + })) + assert.NoError(t, err) + } + tests := []struct { + desc string + cfg EncoderConfig + expected string + }{ + { + desc: "time.Time ISO8601", + cfg: EncoderConfig{ + EncodeDuration: NanosDurationEncoder, + EncodeTime: ISO8601TimeEncoder, + }, + expected: `"k":"2000-01-02T03:04:05.000Z","a":["2000-01-02T03:04:05.000Z"]`, + }, + { + desc: "time.Time RFC3339", + cfg: EncoderConfig{ + EncodeDuration: NanosDurationEncoder, + EncodeTime: RFC3339TimeEncoder, + }, + expected: `"k":"2000-01-02T03:04:05Z","a":["2000-01-02T03:04:05Z"]`, + }, + { + desc: "time.Time RFC3339Nano", + cfg: EncoderConfig{ + EncodeDuration: NanosDurationEncoder, + EncodeTime: RFC3339NanoTimeEncoder, + }, + expected: `"k":"2000-01-02T03:04:05.000000006Z","a":["2000-01-02T03:04:05.000000006Z"]`, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + assertOutput(t, tt.cfg, tt.expected, f) + }) + } +} + +func TestJSONEncoderArrays(t *testing.T) { + tests := []struct { + desc string + expected string // expect f to be called twice + f func(ArrayEncoder) + }{ + {"bool", `[true,true]`, func(e ArrayEncoder) { e.AppendBool(true) }}, + {"byteString", `["k","k"]`, func(e ArrayEncoder) { e.AppendByteString([]byte("k")) }}, + {"byteString", `["k\\","k\\"]`, func(e ArrayEncoder) { e.AppendByteString([]byte(`k\`)) }}, + {"complex128", `["1+2i","1+2i"]`, func(e ArrayEncoder) { e.AppendComplex128(1 + 2i) }}, + {"complex64", `["1+2i","1+2i"]`, func(e ArrayEncoder) { e.AppendComplex64(1 + 2i) }}, + {"durations", `[0.000000002,0.000000002]`, func(e ArrayEncoder) { e.AppendDuration(2) }}, + {"float64", `[3.14,3.14]`, func(e ArrayEncoder) { e.AppendFloat64(3.14) }}, + {"float32", `[3.14,3.14]`, func(e ArrayEncoder) { e.AppendFloat32(3.14) }}, + {"int", `[42,42]`, func(e ArrayEncoder) { e.AppendInt(42) }}, + {"int64", `[42,42]`, func(e ArrayEncoder) { e.AppendInt64(42) }}, + {"int32", `[42,42]`, func(e ArrayEncoder) { e.AppendInt32(42) }}, + {"int16", `[42,42]`, func(e ArrayEncoder) { e.AppendInt16(42) }}, + {"int8", `[42,42]`, func(e ArrayEncoder) { e.AppendInt8(42) }}, + {"string", `["k","k"]`, func(e ArrayEncoder) { e.AppendString("k") }}, + {"string", `["k\\","k\\"]`, func(e ArrayEncoder) { e.AppendString(`k\`) }}, + {"times", `[1,1]`, func(e ArrayEncoder) { e.AppendTime(time.Unix(1, 0)) }}, + {"uint", `[42,42]`, func(e ArrayEncoder) { e.AppendUint(42) }}, + {"uint64", `[42,42]`, func(e ArrayEncoder) { e.AppendUint64(42) }}, + {"uint32", `[42,42]`, func(e ArrayEncoder) { e.AppendUint32(42) }}, + {"uint16", `[42,42]`, func(e ArrayEncoder) { e.AppendUint16(42) }}, + {"uint8", `[42,42]`, func(e ArrayEncoder) { e.AppendUint8(42) }}, + {"uintptr", `[42,42]`, func(e ArrayEncoder) { e.AppendUintptr(42) }}, + { + desc: "arrays (success)", + expected: `[[true],[true]]`, + f: func(arr ArrayEncoder) { + assert.NoError(t, arr.AppendArray(ArrayMarshalerFunc(func(inner ArrayEncoder) error { + inner.AppendBool(true) + return nil + })), "Unexpected error appending an array.") + }, + }, + { + desc: "arrays (error)", + expected: `[[true],[true]]`, + f: func(arr ArrayEncoder) { + assert.Error(t, arr.AppendArray(ArrayMarshalerFunc(func(inner ArrayEncoder) error { + inner.AppendBool(true) + return errors.New("fail") + })), "Expected an error appending an array.") + }, + }, + { + desc: "objects (success)", + expected: `[{"loggable":"yes"},{"loggable":"yes"}]`, + f: func(arr ArrayEncoder) { + assert.NoError(t, arr.AppendObject(loggable{true}), "Unexpected error appending an object.") + }, + }, + { + desc: "objects (error)", + expected: `[{},{}]`, + f: func(arr ArrayEncoder) { + assert.Error(t, arr.AppendObject(loggable{false}), "Expected an error appending an object.") + }, + }, + { + desc: "reflect (success)", + expected: `[{"foo":5},{"foo":5}]`, + f: func(arr ArrayEncoder) { + assert.NoError( + t, + arr.AppendReflected(map[string]int{"foo": 5}), + "Unexpected an error appending an object with reflection.", + ) + }, + }, + { + desc: "reflect (error)", + expected: `[]`, + f: func(arr ArrayEncoder) { + assert.Error( + t, + arr.AppendReflected(noJSON{}), + "Unexpected an error appending an object with reflection.", + ) + }, + }, + { + desc: "object (no nested namespace) then string", + expected: `[{"obj-out":"obj-outside-namespace"},"should-be-outside-obj",{"obj-out":"obj-outside-namespace"},"should-be-outside-obj"]`, + f: func(arr ArrayEncoder) { + assert.NoError(t, arr.AppendObject(maybeNamespace{false})) + arr.AppendString("should-be-outside-obj") + }, + }, + { + desc: "object (with nested namespace) then string", + expected: `[{"obj-out":"obj-outside-namespace","obj-namespace":{"obj-in":"obj-inside-namespace"}},"should-be-outside-obj",{"obj-out":"obj-outside-namespace","obj-namespace":{"obj-in":"obj-inside-namespace"}},"should-be-outside-obj"]`, + f: func(arr ArrayEncoder) { + assert.NoError(t, arr.AppendObject(maybeNamespace{true})) + arr.AppendString("should-be-outside-obj") + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + f := func(enc Encoder) error { + return enc.AddArray("array", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + tt.f(arr) + tt.f(arr) + return nil + })) + } + assertOutput(t, _defaultEncoderConfig, `"array":`+tt.expected, func(enc Encoder) { + err := f(enc) + assert.NoError(t, err, "Unexpected error adding array to JSON encoder.") + }) + }) + } +} + +func TestJSONEncoderTimeArrays(t *testing.T) { + times := []time.Time{ + time.Unix(1008720000, 0).UTC(), // 2001-12-19 + time.Unix(1040169600, 0).UTC(), // 2002-12-18 + time.Unix(1071619200, 0).UTC(), // 2003-12-17 + } + + tests := []struct { + desc string + encoder TimeEncoder + want string + }{ + { + desc: "epoch", + encoder: EpochTimeEncoder, + want: `[1008720000,1040169600,1071619200]`, + }, + { + desc: "epoch millis", + encoder: EpochMillisTimeEncoder, + want: `[1008720000000,1040169600000,1071619200000]`, + }, + { + desc: "iso8601", + encoder: ISO8601TimeEncoder, + want: `["2001-12-19T00:00:00.000Z","2002-12-18T00:00:00.000Z","2003-12-17T00:00:00.000Z"]`, + }, + { + desc: "rfc3339", + encoder: RFC3339TimeEncoder, + want: `["2001-12-19T00:00:00Z","2002-12-18T00:00:00Z","2003-12-17T00:00:00Z"]`, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + cfg := _defaultEncoderConfig + cfg.EncodeTime = tt.encoder + + enc := &jsonEncoder{buf: bufferpool.Get(), EncoderConfig: &cfg} + err := enc.AddArray("array", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + for _, time := range times { + arr.AppendTime(time) + } + return nil + })) + assert.NoError(t, err) + assert.Equal(t, `"array":`+tt.want, enc.buf.String()) + }) + } +} + +func assertJSON(t *testing.T, expected string, enc *jsonEncoder) { + assert.Equal(t, expected, enc.buf.String(), "Encoded JSON didn't match expectations.") +} + +func assertOutput(t testing.TB, cfg EncoderConfig, expected string, f func(Encoder)) { + enc := NewJSONEncoder(cfg).(*jsonEncoder) + f(enc) + assert.Equal(t, expected, enc.buf.String(), "Unexpected encoder output after adding.") + + enc.truncate() + enc.AddString("foo", "bar") + f(enc) + expectedPrefix := `"foo":"bar"` + if expected != "" { + // If we expect output, it should be comma-separated from the previous + // field. + expectedPrefix += "," + } + assert.Equal(t, expectedPrefix+expected, enc.buf.String(), "Unexpected encoder output after adding as a second field.") +} + +// Nested Array- and ObjectMarshalers. +type turducken struct{} + +func (t turducken) MarshalLogObject(enc ObjectEncoder) error { + return enc.AddArray("ducks", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + for i := 0; i < 2; i++ { + err := arr.AppendObject(ObjectMarshalerFunc(func(inner ObjectEncoder) error { + inner.AddString("in", "chicken") + return nil + })) + if err != nil { + return err + } + } + return nil + })) +} + +type turduckens int + +func (t turduckens) MarshalLogArray(enc ArrayEncoder) error { + var err error + tur := turducken{} + for i := 0; i < int(t); i++ { + err = multierr.Append(err, enc.AppendObject(tur)) + } + return err +} + +type loggable struct{ bool } + +func (l loggable) MarshalLogObject(enc ObjectEncoder) error { + if !l.bool { + return errors.New("can't marshal") + } + enc.AddString("loggable", "yes") + return nil +} + +func (l loggable) MarshalLogArray(enc ArrayEncoder) error { + if !l.bool { + return errors.New("can't marshal") + } + enc.AppendBool(true) + return nil +} + +// maybeNamespace is an ObjectMarshaler that sometimes opens a namespace +type maybeNamespace struct{ bool } + +func (m maybeNamespace) MarshalLogObject(enc ObjectEncoder) error { + enc.AddString("obj-out", "obj-outside-namespace") + if m.bool { + enc.OpenNamespace("obj-namespace") + enc.AddString("obj-in", "obj-inside-namespace") + } + return nil +} + +type noJSON struct{} + +func (nj noJSON) MarshalJSON() ([]byte, error) { + return nil, errors.New("no") +} + +func zapEncode(encode func(*jsonEncoder, string)) func(s string) []byte { + return func(s string) []byte { + enc := &jsonEncoder{buf: bufferpool.Get()} + // Escape and quote a string using our encoder. + var ret []byte + encode(enc, s) + ret = make([]byte, 0, enc.buf.Len()+2) + ret = append(ret, '"') + ret = append(ret, enc.buf.Bytes()...) + ret = append(ret, '"') + return ret + } +} + +func roundTripsCorrectly(encode func(string) []byte, original string) bool { + // Encode using our encoder, decode using the standard library, and assert + // that we haven't lost any information. + encoded := encode(original) + + var decoded string + err := json.Unmarshal(encoded, &decoded) + if err != nil { + return false + } + return original == decoded +} + +func roundTripsCorrectlyString(original string) bool { + return roundTripsCorrectly(zapEncode((*jsonEncoder).safeAddString), original) +} + +func roundTripsCorrectlyByteString(original string) bool { + return roundTripsCorrectly( + zapEncode(func(enc *jsonEncoder, s string) { + enc.safeAddByteString([]byte(s)) + }), + original) +} + +type ASCII string + +func (s ASCII) Generate(r *rand.Rand, size int) reflect.Value { + bs := make([]byte, size) + for i := range bs { + bs[i] = byte(r.Intn(128)) + } + a := ASCII(bs) + return reflect.ValueOf(a) +} + +func asciiRoundTripsCorrectlyString(s ASCII) bool { + return roundTripsCorrectlyString(string(s)) +} + +func asciiRoundTripsCorrectlyByteString(s ASCII) bool { + return roundTripsCorrectlyByteString(string(s)) +} + +func TestJSONQuick(t *testing.T) { + check := func(f interface{}) { + err := quick.Check(f, &quick.Config{MaxCountScale: 100.0}) + assert.NoError(t, err) + } + // Test the full range of UTF-8 strings. + check(roundTripsCorrectlyString) + check(roundTripsCorrectlyByteString) + + // Focus on ASCII strings. + check(asciiRoundTripsCorrectlyString) + check(asciiRoundTripsCorrectlyByteString) +} + +var _stringLikeCorpus = []string{ + "", + "foo", + "bar", + "a\nb", + "a\tb", + "a\\b", + `a"b`, +} + +func FuzzSafeAppendStringLike_bytes(f *testing.F) { + for _, s := range _stringLikeCorpus { + f.Add([]byte(s)) + } + f.Fuzz(func(t *testing.T, b []byte) { + if !utf8.Valid(b) { + t.Skip() + } + + fuzzSafeAppendStringLike(t, string(b), func(buf *buffer.Buffer) { + safeAppendStringLike( + (*buffer.Buffer).AppendBytes, + utf8.DecodeRune, + buf, + b, + ) + }) + }) +} + +func FuzzSafeAppendStringLike_string(f *testing.F) { + for _, s := range _stringLikeCorpus { + f.Add(s) + } + f.Fuzz(func(t *testing.T, s string) { + if !utf8.ValidString(s) { + t.Skip() + } + + fuzzSafeAppendStringLike(t, s, func(buf *buffer.Buffer) { + safeAppendStringLike( + (*buffer.Buffer).AppendString, + utf8.DecodeRuneInString, + buf, + s, + ) + }) + }) +} + +func fuzzSafeAppendStringLike( + t *testing.T, + want string, + writeString func(*buffer.Buffer), +) { + t.Helper() + + buf := bufferpool.Get() + defer buf.Free() + + buf.AppendByte('"') + writeString(buf) + buf.AppendByte('"') + + var got string + require.NoError(t, json.Unmarshal(buf.Bytes(), &got)) + assert.Equal(t, want, got) +} diff --git a/vendor/go.uber.org/zap/zapcore/lazy_with.go b/vendor/go.uber.org/zap/zapcore/lazy_with.go new file mode 100644 index 0000000000..05288d6a88 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/lazy_with.go @@ -0,0 +1,54 @@ +// Copyright (c) 2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "sync" + +type lazyWithCore struct { + Core + sync.Once + fields []Field +} + +// NewLazyWith wraps a Core with a "lazy" Core that will only encode fields if +// the logger is written to (or is further chained in a lon-lazy manner). +func NewLazyWith(core Core, fields []Field) Core { + return &lazyWithCore{ + Core: core, + fields: fields, + } +} + +func (d *lazyWithCore) initOnce() { + d.Once.Do(func() { + d.Core = d.Core.With(d.fields) + }) +} + +func (d *lazyWithCore) With(fields []Field) Core { + d.initOnce() + return d.Core.With(fields) +} + +func (d *lazyWithCore) Check(e Entry, ce *CheckedEntry) *CheckedEntry { + d.initOnce() + return d.Core.Check(e, ce) +} diff --git a/vendor/go.uber.org/zap/zapcore/leak_test.go b/vendor/go.uber.org/zap/zapcore/leak_test.go new file mode 100644 index 0000000000..4ef412e37c --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/leak_test.go @@ -0,0 +1,31 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "testing" + + "go.uber.org/goleak" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} diff --git a/vendor/go.uber.org/zap/zapcore/level.go b/vendor/go.uber.org/zap/zapcore/level.go new file mode 100644 index 0000000000..e01a241316 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/level.go @@ -0,0 +1,229 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bytes" + "errors" + "fmt" +) + +var errUnmarshalNilLevel = errors.New("can't unmarshal a nil *Level") + +// A Level is a logging priority. Higher levels are more important. +type Level int8 + +const ( + // DebugLevel logs are typically voluminous, and are usually disabled in + // production. + DebugLevel Level = iota - 1 + // InfoLevel is the default logging priority. + InfoLevel + // WarnLevel logs are more important than Info, but don't need individual + // human review. + WarnLevel + // ErrorLevel logs are high-priority. If an application is running smoothly, + // it shouldn't generate any error-level logs. + ErrorLevel + // DPanicLevel logs are particularly important errors. In development the + // logger panics after writing the message. + DPanicLevel + // PanicLevel logs a message, then panics. + PanicLevel + // FatalLevel logs a message, then calls os.Exit(1). + FatalLevel + + _minLevel = DebugLevel + _maxLevel = FatalLevel + + // InvalidLevel is an invalid value for Level. + // + // Core implementations may panic if they see messages of this level. + InvalidLevel = _maxLevel + 1 +) + +// ParseLevel parses a level based on the lower-case or all-caps ASCII +// representation of the log level. If the provided ASCII representation is +// invalid an error is returned. +// +// This is particularly useful when dealing with text input to configure log +// levels. +func ParseLevel(text string) (Level, error) { + var level Level + err := level.UnmarshalText([]byte(text)) + return level, err +} + +type leveledEnabler interface { + LevelEnabler + + Level() Level +} + +// LevelOf reports the minimum enabled log level for the given LevelEnabler +// from Zap's supported log levels, or [InvalidLevel] if none of them are +// enabled. +// +// A LevelEnabler may implement a 'Level() Level' method to override the +// behavior of this function. +// +// func (c *core) Level() Level { +// return c.currentLevel +// } +// +// It is recommended that [Core] implementations that wrap other cores use +// LevelOf to retrieve the level of the wrapped core. For example, +// +// func (c *coreWrapper) Level() Level { +// return zapcore.LevelOf(c.wrappedCore) +// } +func LevelOf(enab LevelEnabler) Level { + if lvler, ok := enab.(leveledEnabler); ok { + return lvler.Level() + } + + for lvl := _minLevel; lvl <= _maxLevel; lvl++ { + if enab.Enabled(lvl) { + return lvl + } + } + + return InvalidLevel +} + +// String returns a lower-case ASCII representation of the log level. +func (l Level) String() string { + switch l { + case DebugLevel: + return "debug" + case InfoLevel: + return "info" + case WarnLevel: + return "warn" + case ErrorLevel: + return "error" + case DPanicLevel: + return "dpanic" + case PanicLevel: + return "panic" + case FatalLevel: + return "fatal" + default: + return fmt.Sprintf("Level(%d)", l) + } +} + +// CapitalString returns an all-caps ASCII representation of the log level. +func (l Level) CapitalString() string { + // Printing levels in all-caps is common enough that we should export this + // functionality. + switch l { + case DebugLevel: + return "DEBUG" + case InfoLevel: + return "INFO" + case WarnLevel: + return "WARN" + case ErrorLevel: + return "ERROR" + case DPanicLevel: + return "DPANIC" + case PanicLevel: + return "PANIC" + case FatalLevel: + return "FATAL" + default: + return fmt.Sprintf("LEVEL(%d)", l) + } +} + +// MarshalText marshals the Level to text. Note that the text representation +// drops the -Level suffix (see example). +func (l Level) MarshalText() ([]byte, error) { + return []byte(l.String()), nil +} + +// UnmarshalText unmarshals text to a level. Like MarshalText, UnmarshalText +// expects the text representation of a Level to drop the -Level suffix (see +// example). +// +// In particular, this makes it easy to configure logging levels using YAML, +// TOML, or JSON files. +func (l *Level) UnmarshalText(text []byte) error { + if l == nil { + return errUnmarshalNilLevel + } + if !l.unmarshalText(text) && !l.unmarshalText(bytes.ToLower(text)) { + return fmt.Errorf("unrecognized level: %q", text) + } + return nil +} + +func (l *Level) unmarshalText(text []byte) bool { + switch string(text) { + case "debug", "DEBUG": + *l = DebugLevel + case "info", "INFO", "": // make the zero value useful + *l = InfoLevel + case "warn", "WARN": + *l = WarnLevel + case "error", "ERROR": + *l = ErrorLevel + case "dpanic", "DPANIC": + *l = DPanicLevel + case "panic", "PANIC": + *l = PanicLevel + case "fatal", "FATAL": + *l = FatalLevel + default: + return false + } + return true +} + +// Set sets the level for the flag.Value interface. +func (l *Level) Set(s string) error { + return l.UnmarshalText([]byte(s)) +} + +// Get gets the level for the flag.Getter interface. +func (l *Level) Get() interface{} { + return *l +} + +// Enabled returns true if the given level is at or above this level. +func (l Level) Enabled(lvl Level) bool { + return lvl >= l +} + +// LevelEnabler decides whether a given logging level is enabled when logging a +// message. +// +// Enablers are intended to be used to implement deterministic filters; +// concerns like sampling are better implemented as a Core. +// +// Each concrete Level value implements a static LevelEnabler which returns +// true for itself and all higher logging levels. For example WarnLevel.Enabled() +// will return true for WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, and +// FatalLevel, but return false for InfoLevel and DebugLevel. +type LevelEnabler interface { + Enabled(Level) bool +} diff --git a/vendor/go.uber.org/zap/zapcore/level_strings.go b/vendor/go.uber.org/zap/zapcore/level_strings.go new file mode 100644 index 0000000000..7af8dadcb3 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/level_strings.go @@ -0,0 +1,46 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "go.uber.org/zap/internal/color" + +var ( + _levelToColor = map[Level]color.Color{ + DebugLevel: color.Magenta, + InfoLevel: color.Blue, + WarnLevel: color.Yellow, + ErrorLevel: color.Red, + DPanicLevel: color.Red, + PanicLevel: color.Red, + FatalLevel: color.Red, + } + _unknownLevelColor = color.Red + + _levelToLowercaseColorString = make(map[Level]string, len(_levelToColor)) + _levelToCapitalColorString = make(map[Level]string, len(_levelToColor)) +) + +func init() { + for level, color := range _levelToColor { + _levelToLowercaseColorString[level] = color.Add(level.String()) + _levelToCapitalColorString[level] = color.Add(level.CapitalString()) + } +} diff --git a/vendor/go.uber.org/zap/zapcore/level_strings_test.go b/vendor/go.uber.org/zap/zapcore/level_strings_test.go new file mode 100644 index 0000000000..14b0bac628 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/level_strings_test.go @@ -0,0 +1,38 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAllLevelsCoveredByLevelString(t *testing.T) { + numLevels := int((_maxLevel - _minLevel) + 1) + + isComplete := func(m map[Level]string) bool { + return len(m) == numLevels + } + + assert.True(t, isComplete(_levelToLowercaseColorString), "Colored lowercase strings don't cover all levels.") + assert.True(t, isComplete(_levelToCapitalColorString), "Colored capital strings don't cover all levels.") +} diff --git a/vendor/go.uber.org/zap/zapcore/level_test.go b/vendor/go.uber.org/zap/zapcore/level_test.go new file mode 100644 index 0000000000..d8eb962921 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/level_test.go @@ -0,0 +1,248 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bytes" + "flag" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLevelString(t *testing.T) { + tests := map[Level]string{ + DebugLevel: "debug", + InfoLevel: "info", + WarnLevel: "warn", + ErrorLevel: "error", + DPanicLevel: "dpanic", + PanicLevel: "panic", + FatalLevel: "fatal", + Level(-42): "Level(-42)", + InvalidLevel: "Level(6)", // InvalidLevel does not have a name + } + + for lvl, stringLevel := range tests { + assert.Equal(t, stringLevel, lvl.String(), "Unexpected lowercase level string.") + assert.Equal(t, strings.ToUpper(stringLevel), lvl.CapitalString(), "Unexpected all-caps level string.") + } +} + +func TestLevelText(t *testing.T) { + tests := []struct { + text string + level Level + }{ + {"debug", DebugLevel}, + {"info", InfoLevel}, + {"", InfoLevel}, // make the zero value useful + {"warn", WarnLevel}, + {"error", ErrorLevel}, + {"dpanic", DPanicLevel}, + {"panic", PanicLevel}, + {"fatal", FatalLevel}, + } + for _, tt := range tests { + if tt.text != "" { + lvl := tt.level + marshaled, err := lvl.MarshalText() + assert.NoError(t, err, "Unexpected error marshaling level %v to text.", &lvl) + assert.Equal(t, tt.text, string(marshaled), "Marshaling level %v to text yielded unexpected result.", &lvl) + } + + var unmarshaled Level + err := unmarshaled.UnmarshalText([]byte(tt.text)) + assert.NoError(t, err, `Unexpected error unmarshaling text %q to level.`, tt.text) + assert.Equal(t, tt.level, unmarshaled, `Text %q unmarshaled to an unexpected level.`, tt.text) + } +} + +func TestParseLevel(t *testing.T) { + tests := []struct { + text string + level Level + err string + }{ + {"info", InfoLevel, ""}, + {"DEBUG", DebugLevel, ""}, + {"FOO", 0, `unrecognized level: "FOO"`}, + } + for _, tt := range tests { + parsedLevel, err := ParseLevel(tt.text) + if len(tt.err) == 0 { + assert.NoError(t, err) + assert.Equal(t, tt.level, parsedLevel) + } else { + assert.ErrorContains(t, err, tt.err) + } + } +} + +func TestCapitalLevelsParse(t *testing.T) { + tests := []struct { + text string + level Level + }{ + {"DEBUG", DebugLevel}, + {"INFO", InfoLevel}, + {"WARN", WarnLevel}, + {"ERROR", ErrorLevel}, + {"DPANIC", DPanicLevel}, + {"PANIC", PanicLevel}, + {"FATAL", FatalLevel}, + } + for _, tt := range tests { + var unmarshaled Level + err := unmarshaled.UnmarshalText([]byte(tt.text)) + assert.NoError(t, err, `Unexpected error unmarshaling text %q to level.`, tt.text) + assert.Equal(t, tt.level, unmarshaled, `Text %q unmarshaled to an unexpected level.`, tt.text) + } +} + +func TestWeirdLevelsParse(t *testing.T) { + tests := []struct { + text string + level Level + }{ + // I guess... + {"Debug", DebugLevel}, + {"Info", InfoLevel}, + {"Warn", WarnLevel}, + {"Error", ErrorLevel}, + {"Dpanic", DPanicLevel}, + {"Panic", PanicLevel}, + {"Fatal", FatalLevel}, + + // What even is... + {"DeBuG", DebugLevel}, + {"InFo", InfoLevel}, + {"WaRn", WarnLevel}, + {"ErRor", ErrorLevel}, + {"DpAnIc", DPanicLevel}, + {"PaNiC", PanicLevel}, + {"FaTaL", FatalLevel}, + } + for _, tt := range tests { + var unmarshaled Level + err := unmarshaled.UnmarshalText([]byte(tt.text)) + assert.NoError(t, err, `Unexpected error unmarshaling text %q to level.`, tt.text) + assert.Equal(t, tt.level, unmarshaled, `Text %q unmarshaled to an unexpected level.`, tt.text) + } +} + +func TestLevelNils(t *testing.T) { + var l *Level + + // The String() method will not handle nil level properly. + assert.Panics(t, func() { + assert.Equal(t, "Level(nil)", l.String(), "Unexpected result stringifying nil *Level.") + }, "Level(nil).String() should panic") + + assert.Panics(t, func() { + _, _ = l.MarshalText() // should panic + }, "Expected to panic when marshalling a nil level.") + + err := l.UnmarshalText([]byte("debug")) + assert.Equal(t, errUnmarshalNilLevel, err, "Expected to error unmarshalling into a nil Level.") +} + +func TestLevelUnmarshalUnknownText(t *testing.T) { + var l Level + err := l.UnmarshalText([]byte("foo")) + assert.ErrorContains(t, err, "unrecognized level", "Expected unmarshaling arbitrary text to fail.") +} + +func TestLevelAsFlagValue(t *testing.T) { + var ( + buf bytes.Buffer + lvl Level + ) + fs := flag.NewFlagSet("levelTest", flag.ContinueOnError) + fs.SetOutput(&buf) + fs.Var(&lvl, "level", "log level") + + for _, expected := range []Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel} { + assert.NoError(t, fs.Parse([]string{"-level", expected.String()})) + assert.Equal(t, expected, lvl, "Unexpected level after parsing flag.") + assert.Equal(t, expected, lvl.Get(), "Unexpected output using flag.Getter API.") + assert.Empty(t, buf.String(), "Unexpected error output parsing level flag.") + buf.Reset() + } + + assert.Error(t, fs.Parse([]string{"-level", "nope"})) + assert.Equal( + t, + `invalid value "nope" for flag -level: unrecognized level: "nope"`, + strings.Split(buf.String(), "\n")[0], // second line is help message + "Unexpected error output from invalid flag input.", + ) +} + +// enablerWithCustomLevel is a LevelEnabler that implements a custom Level +// method. +type enablerWithCustomLevel struct{ lvl Level } + +var _ leveledEnabler = (*enablerWithCustomLevel)(nil) + +func (l *enablerWithCustomLevel) Enabled(lvl Level) bool { + return l.lvl.Enabled(lvl) +} + +func (l *enablerWithCustomLevel) Level() Level { + return l.lvl +} + +func TestLevelOf(t *testing.T) { + tests := []struct { + desc string + give LevelEnabler + want Level + }{ + {desc: "debug", give: DebugLevel, want: DebugLevel}, + {desc: "info", give: InfoLevel, want: InfoLevel}, + {desc: "warn", give: WarnLevel, want: WarnLevel}, + {desc: "error", give: ErrorLevel, want: ErrorLevel}, + {desc: "dpanic", give: DPanicLevel, want: DPanicLevel}, + {desc: "panic", give: PanicLevel, want: PanicLevel}, + {desc: "fatal", give: FatalLevel, want: FatalLevel}, + { + desc: "leveledEnabler", + give: &enablerWithCustomLevel{lvl: InfoLevel}, + want: InfoLevel, + }, + { + desc: "noop", + give: NewNopCore(), // always disabled + want: InvalidLevel, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + assert.Equal(t, tt.want, LevelOf(tt.give), "Reported level did not match.") + }) + } +} diff --git a/vendor/go.uber.org/zap/zapcore/marshaler.go b/vendor/go.uber.org/zap/zapcore/marshaler.go new file mode 100644 index 0000000000..c3c55ba0d9 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/marshaler.go @@ -0,0 +1,61 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +// ObjectMarshaler allows user-defined types to efficiently add themselves to the +// logging context, and to selectively omit information which shouldn't be +// included in logs (e.g., passwords). +// +// Note: ObjectMarshaler is only used when zap.Object is used or when +// passed directly to zap.Any. It is not used when reflection-based +// encoding is used. +type ObjectMarshaler interface { + MarshalLogObject(ObjectEncoder) error +} + +// ObjectMarshalerFunc is a type adapter that turns a function into an +// ObjectMarshaler. +type ObjectMarshalerFunc func(ObjectEncoder) error + +// MarshalLogObject calls the underlying function. +func (f ObjectMarshalerFunc) MarshalLogObject(enc ObjectEncoder) error { + return f(enc) +} + +// ArrayMarshaler allows user-defined types to efficiently add themselves to the +// logging context, and to selectively omit information which shouldn't be +// included in logs (e.g., passwords). +// +// Note: ArrayMarshaler is only used when zap.Array is used or when +// passed directly to zap.Any. It is not used when reflection-based +// encoding is used. +type ArrayMarshaler interface { + MarshalLogArray(ArrayEncoder) error +} + +// ArrayMarshalerFunc is a type adapter that turns a function into an +// ArrayMarshaler. +type ArrayMarshalerFunc func(ArrayEncoder) error + +// MarshalLogArray calls the underlying function. +func (f ArrayMarshalerFunc) MarshalLogArray(enc ArrayEncoder) error { + return f(enc) +} diff --git a/vendor/go.uber.org/zap/zapcore/memory_encoder.go b/vendor/go.uber.org/zap/zapcore/memory_encoder.go new file mode 100644 index 0000000000..dfead0829d --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/memory_encoder.go @@ -0,0 +1,179 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "time" + +// MapObjectEncoder is an ObjectEncoder backed by a simple +// map[string]interface{}. It's not fast enough for production use, but it's +// helpful in tests. +type MapObjectEncoder struct { + // Fields contains the entire encoded log context. + Fields map[string]interface{} + // cur is a pointer to the namespace we're currently writing to. + cur map[string]interface{} +} + +// NewMapObjectEncoder creates a new map-backed ObjectEncoder. +func NewMapObjectEncoder() *MapObjectEncoder { + m := make(map[string]interface{}) + return &MapObjectEncoder{ + Fields: m, + cur: m, + } +} + +// AddArray implements ObjectEncoder. +func (m *MapObjectEncoder) AddArray(key string, v ArrayMarshaler) error { + arr := &sliceArrayEncoder{elems: make([]interface{}, 0)} + err := v.MarshalLogArray(arr) + m.cur[key] = arr.elems + return err +} + +// AddObject implements ObjectEncoder. +func (m *MapObjectEncoder) AddObject(k string, v ObjectMarshaler) error { + newMap := NewMapObjectEncoder() + m.cur[k] = newMap.Fields + return v.MarshalLogObject(newMap) +} + +// AddBinary implements ObjectEncoder. +func (m *MapObjectEncoder) AddBinary(k string, v []byte) { m.cur[k] = v } + +// AddByteString implements ObjectEncoder. +func (m *MapObjectEncoder) AddByteString(k string, v []byte) { m.cur[k] = string(v) } + +// AddBool implements ObjectEncoder. +func (m *MapObjectEncoder) AddBool(k string, v bool) { m.cur[k] = v } + +// AddDuration implements ObjectEncoder. +func (m MapObjectEncoder) AddDuration(k string, v time.Duration) { m.cur[k] = v } + +// AddComplex128 implements ObjectEncoder. +func (m *MapObjectEncoder) AddComplex128(k string, v complex128) { m.cur[k] = v } + +// AddComplex64 implements ObjectEncoder. +func (m *MapObjectEncoder) AddComplex64(k string, v complex64) { m.cur[k] = v } + +// AddFloat64 implements ObjectEncoder. +func (m *MapObjectEncoder) AddFloat64(k string, v float64) { m.cur[k] = v } + +// AddFloat32 implements ObjectEncoder. +func (m *MapObjectEncoder) AddFloat32(k string, v float32) { m.cur[k] = v } + +// AddInt implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt(k string, v int) { m.cur[k] = v } + +// AddInt64 implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt64(k string, v int64) { m.cur[k] = v } + +// AddInt32 implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt32(k string, v int32) { m.cur[k] = v } + +// AddInt16 implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt16(k string, v int16) { m.cur[k] = v } + +// AddInt8 implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt8(k string, v int8) { m.cur[k] = v } + +// AddString implements ObjectEncoder. +func (m *MapObjectEncoder) AddString(k string, v string) { m.cur[k] = v } + +// AddTime implements ObjectEncoder. +func (m MapObjectEncoder) AddTime(k string, v time.Time) { m.cur[k] = v } + +// AddUint implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint(k string, v uint) { m.cur[k] = v } + +// AddUint64 implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint64(k string, v uint64) { m.cur[k] = v } + +// AddUint32 implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint32(k string, v uint32) { m.cur[k] = v } + +// AddUint16 implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint16(k string, v uint16) { m.cur[k] = v } + +// AddUint8 implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint8(k string, v uint8) { m.cur[k] = v } + +// AddUintptr implements ObjectEncoder. +func (m *MapObjectEncoder) AddUintptr(k string, v uintptr) { m.cur[k] = v } + +// AddReflected implements ObjectEncoder. +func (m *MapObjectEncoder) AddReflected(k string, v interface{}) error { + m.cur[k] = v + return nil +} + +// OpenNamespace implements ObjectEncoder. +func (m *MapObjectEncoder) OpenNamespace(k string) { + ns := make(map[string]interface{}) + m.cur[k] = ns + m.cur = ns +} + +// sliceArrayEncoder is an ArrayEncoder backed by a simple []interface{}. Like +// the MapObjectEncoder, it's not designed for production use. +type sliceArrayEncoder struct { + elems []interface{} +} + +func (s *sliceArrayEncoder) AppendArray(v ArrayMarshaler) error { + enc := &sliceArrayEncoder{} + err := v.MarshalLogArray(enc) + s.elems = append(s.elems, enc.elems) + return err +} + +func (s *sliceArrayEncoder) AppendObject(v ObjectMarshaler) error { + m := NewMapObjectEncoder() + err := v.MarshalLogObject(m) + s.elems = append(s.elems, m.Fields) + return err +} + +func (s *sliceArrayEncoder) AppendReflected(v interface{}) error { + s.elems = append(s.elems, v) + return nil +} + +func (s *sliceArrayEncoder) AppendBool(v bool) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendByteString(v []byte) { s.elems = append(s.elems, string(v)) } +func (s *sliceArrayEncoder) AppendComplex128(v complex128) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendComplex64(v complex64) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendDuration(v time.Duration) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendFloat64(v float64) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendFloat32(v float32) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt(v int) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt64(v int64) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt32(v int32) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt16(v int16) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt8(v int8) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendString(v string) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendTime(v time.Time) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint(v uint) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint64(v uint64) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint32(v uint32) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint16(v uint16) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint8(v uint8) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUintptr(v uintptr) { s.elems = append(s.elems, v) } diff --git a/vendor/go.uber.org/zap/zapcore/memory_encoder_test.go b/vendor/go.uber.org/zap/zapcore/memory_encoder_test.go new file mode 100644 index 0000000000..d5f215fb62 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/memory_encoder_test.go @@ -0,0 +1,370 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMapObjectEncoderAdd(t *testing.T) { + // Expected output of a turducken. + wantTurducken := map[string]interface{}{ + "ducks": []interface{}{ + map[string]interface{}{"in": "chicken"}, + map[string]interface{}{"in": "chicken"}, + }, + } + + tests := []struct { + desc string + f func(ObjectEncoder) + expected interface{} + }{ + { + desc: "AddObject", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddObject("k", loggable{true}), "Expected AddObject to succeed.") + }, + expected: map[string]interface{}{"loggable": "yes"}, + }, + { + desc: "AddObject (nested)", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddObject("k", turducken{}), "Expected AddObject to succeed.") + }, + expected: wantTurducken, + }, + { + desc: "AddArray", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddArray("k", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + arr.AppendBool(true) + arr.AppendBool(false) + arr.AppendBool(true) + return nil + })), "Expected AddArray to succeed.") + }, + expected: []interface{}{true, false, true}, + }, + { + desc: "AddArray (nested)", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddArray("k", turduckens(2)), "Expected AddArray to succeed.") + }, + expected: []interface{}{wantTurducken, wantTurducken}, + }, + { + desc: "AddArray (empty)", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddArray("k", turduckens(0)), "Expected AddArray to succeed.") + }, + expected: []interface{}{}, + }, + { + desc: "AddBinary", + f: func(e ObjectEncoder) { e.AddBinary("k", []byte("foo")) }, + expected: []byte("foo"), + }, + { + desc: "AddByteString", + f: func(e ObjectEncoder) { e.AddByteString("k", []byte("foo")) }, + expected: "foo", + }, + { + desc: "AddBool", + f: func(e ObjectEncoder) { e.AddBool("k", true) }, + expected: true, + }, + { + desc: "AddComplex128", + f: func(e ObjectEncoder) { e.AddComplex128("k", 1+2i) }, + expected: 1 + 2i, + }, + { + desc: "AddComplex64", + f: func(e ObjectEncoder) { e.AddComplex64("k", 1+2i) }, + expected: complex64(1 + 2i), + }, + { + desc: "AddDuration", + f: func(e ObjectEncoder) { e.AddDuration("k", time.Millisecond) }, + expected: time.Millisecond, + }, + { + desc: "AddFloat64", + f: func(e ObjectEncoder) { e.AddFloat64("k", 3.14) }, + expected: 3.14, + }, + { + desc: "AddFloat32", + f: func(e ObjectEncoder) { e.AddFloat32("k", 3.14) }, + expected: float32(3.14), + }, + { + desc: "AddInt", + f: func(e ObjectEncoder) { e.AddInt("k", 42) }, + expected: 42, + }, + { + desc: "AddInt64", + f: func(e ObjectEncoder) { e.AddInt64("k", 42) }, + expected: int64(42), + }, + { + desc: "AddInt32", + f: func(e ObjectEncoder) { e.AddInt32("k", 42) }, + expected: int32(42), + }, + { + desc: "AddInt16", + f: func(e ObjectEncoder) { e.AddInt16("k", 42) }, + expected: int16(42), + }, + { + desc: "AddInt8", + f: func(e ObjectEncoder) { e.AddInt8("k", 42) }, + expected: int8(42), + }, + { + desc: "AddString", + f: func(e ObjectEncoder) { e.AddString("k", "v") }, + expected: "v", + }, + { + desc: "AddTime", + f: func(e ObjectEncoder) { e.AddTime("k", time.Unix(0, 100)) }, + expected: time.Unix(0, 100), + }, + { + desc: "AddUint", + f: func(e ObjectEncoder) { e.AddUint("k", 42) }, + expected: uint(42), + }, + { + desc: "AddUint64", + f: func(e ObjectEncoder) { e.AddUint64("k", 42) }, + expected: uint64(42), + }, + { + desc: "AddUint32", + f: func(e ObjectEncoder) { e.AddUint32("k", 42) }, + expected: uint32(42), + }, + { + desc: "AddUint16", + f: func(e ObjectEncoder) { e.AddUint16("k", 42) }, + expected: uint16(42), + }, + { + desc: "AddUint8", + f: func(e ObjectEncoder) { e.AddUint8("k", 42) }, + expected: uint8(42), + }, + { + desc: "AddUintptr", + f: func(e ObjectEncoder) { e.AddUintptr("k", 42) }, + expected: uintptr(42), + }, + { + desc: "AddReflected", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddReflected("k", map[string]interface{}{"foo": 5}), "Expected AddReflected to succeed.") + }, + expected: map[string]interface{}{"foo": 5}, + }, + { + desc: "OpenNamespace", + f: func(e ObjectEncoder) { + e.OpenNamespace("k") + e.AddInt("foo", 1) + e.OpenNamespace("middle") + e.AddInt("foo", 2) + e.OpenNamespace("inner") + e.AddInt("foo", 3) + }, + expected: map[string]interface{}{ + "foo": 1, + "middle": map[string]interface{}{ + "foo": 2, + "inner": map[string]interface{}{ + "foo": 3, + }, + }, + }, + }, + { + desc: "object (no nested namespace) then string", + f: func(e ObjectEncoder) { + e.OpenNamespace("k") + assert.NoError(t, e.AddObject("obj", maybeNamespace{false})) + e.AddString("not-obj", "should-be-outside-obj") + }, + expected: map[string]interface{}{ + "obj": map[string]interface{}{ + "obj-out": "obj-outside-namespace", + }, + "not-obj": "should-be-outside-obj", + }, + }, + { + desc: "object (with nested namespace) then string", + f: func(e ObjectEncoder) { + e.OpenNamespace("k") + assert.NoError(t, e.AddObject("obj", maybeNamespace{true})) + e.AddString("not-obj", "should-be-outside-obj") + }, + expected: map[string]interface{}{ + "obj": map[string]interface{}{ + "obj-out": "obj-outside-namespace", + "obj-namespace": map[string]interface{}{ + "obj-in": "obj-inside-namespace", + }, + }, + "not-obj": "should-be-outside-obj", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + enc := NewMapObjectEncoder() + tt.f(enc) + assert.Equal(t, tt.expected, enc.Fields["k"], "Unexpected encoder output.") + }) + } +} + +func TestSliceArrayEncoderAppend(t *testing.T) { + tests := []struct { + desc string + f func(ArrayEncoder) + expected interface{} + }{ + // AppendObject and AppendArray are covered by the AddObject (nested) and + // AddArray (nested) cases above. + {"AppendBool", func(e ArrayEncoder) { e.AppendBool(true) }, true}, + {"AppendByteString", func(e ArrayEncoder) { e.AppendByteString([]byte("foo")) }, "foo"}, + {"AppendComplex128", func(e ArrayEncoder) { e.AppendComplex128(1 + 2i) }, 1 + 2i}, + {"AppendComplex64", func(e ArrayEncoder) { e.AppendComplex64(1 + 2i) }, complex64(1 + 2i)}, + {"AppendDuration", func(e ArrayEncoder) { e.AppendDuration(time.Second) }, time.Second}, + {"AppendFloat64", func(e ArrayEncoder) { e.AppendFloat64(3.14) }, 3.14}, + {"AppendFloat32", func(e ArrayEncoder) { e.AppendFloat32(3.14) }, float32(3.14)}, + {"AppendInt", func(e ArrayEncoder) { e.AppendInt(42) }, 42}, + {"AppendInt64", func(e ArrayEncoder) { e.AppendInt64(42) }, int64(42)}, + {"AppendInt32", func(e ArrayEncoder) { e.AppendInt32(42) }, int32(42)}, + {"AppendInt16", func(e ArrayEncoder) { e.AppendInt16(42) }, int16(42)}, + {"AppendInt8", func(e ArrayEncoder) { e.AppendInt8(42) }, int8(42)}, + {"AppendString", func(e ArrayEncoder) { e.AppendString("foo") }, "foo"}, + {"AppendTime", func(e ArrayEncoder) { e.AppendTime(time.Unix(0, 100)) }, time.Unix(0, 100)}, + {"AppendUint", func(e ArrayEncoder) { e.AppendUint(42) }, uint(42)}, + {"AppendUint64", func(e ArrayEncoder) { e.AppendUint64(42) }, uint64(42)}, + {"AppendUint32", func(e ArrayEncoder) { e.AppendUint32(42) }, uint32(42)}, + {"AppendUint16", func(e ArrayEncoder) { e.AppendUint16(42) }, uint16(42)}, + {"AppendUint8", func(e ArrayEncoder) { e.AppendUint8(42) }, uint8(42)}, + {"AppendUintptr", func(e ArrayEncoder) { e.AppendUintptr(42) }, uintptr(42)}, + { + desc: "AppendReflected", + f: func(e ArrayEncoder) { + assert.NoError(t, e.AppendReflected(map[string]interface{}{"foo": 5})) + }, + expected: map[string]interface{}{"foo": 5}, + }, + { + desc: "AppendArray (arrays of arrays)", + f: func(e ArrayEncoder) { + err := e.AppendArray(ArrayMarshalerFunc(func(inner ArrayEncoder) error { + inner.AppendBool(true) + inner.AppendBool(false) + return nil + })) + assert.NoError(t, err) + }, + expected: []interface{}{true, false}, + }, + { + desc: "object (no nested namespace) then string", + f: func(e ArrayEncoder) { + err := e.AppendArray(ArrayMarshalerFunc(func(inner ArrayEncoder) error { + err := inner.AppendObject(maybeNamespace{false}) + inner.AppendString("should-be-outside-obj") + return err + })) + assert.NoError(t, err) + }, + expected: []interface{}{ + map[string]interface{}{ + "obj-out": "obj-outside-namespace", + }, + "should-be-outside-obj", + }, + }, + { + desc: "object (with nested namespace) then string", + f: func(e ArrayEncoder) { + err := e.AppendArray(ArrayMarshalerFunc(func(inner ArrayEncoder) error { + err := inner.AppendObject(maybeNamespace{true}) + inner.AppendString("should-be-outside-obj") + return err + })) + assert.NoError(t, err) + }, + expected: []interface{}{ + map[string]interface{}{ + "obj-out": "obj-outside-namespace", + "obj-namespace": map[string]interface{}{ + "obj-in": "obj-inside-namespace", + }, + }, + "should-be-outside-obj", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + enc := NewMapObjectEncoder() + assert.NoError(t, enc.AddArray("k", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + tt.f(arr) + tt.f(arr) + return nil + })), "Expected AddArray to succeed.") + + arr, ok := enc.Fields["k"].([]interface{}) + require.True(t, ok, "Test case %s didn't encode an array.", tt.desc) + assert.Equal(t, []interface{}{tt.expected, tt.expected}, arr, "Unexpected encoder output.") + }) + } +} + +func TestMapObjectEncoderReflectionFailures(t *testing.T) { + enc := NewMapObjectEncoder() + assert.Error(t, enc.AddObject("object", loggable{false}), "Expected AddObject to fail.") + assert.Equal( + t, + map[string]interface{}{"object": map[string]interface{}{}}, + enc.Fields, + "Expected encoder to use empty values on errors.", + ) +} diff --git a/vendor/go.uber.org/zap/zapcore/reflected_encoder.go b/vendor/go.uber.org/zap/zapcore/reflected_encoder.go new file mode 100644 index 0000000000..8746360eca --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/reflected_encoder.go @@ -0,0 +1,41 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "encoding/json" + "io" +) + +// ReflectedEncoder serializes log fields that can't be serialized with Zap's +// JSON encoder. These have the ReflectType field type. +// Use EncoderConfig.NewReflectedEncoder to set this. +type ReflectedEncoder interface { + // Encode encodes and writes to the underlying data stream. + Encode(interface{}) error +} + +func defaultReflectedEncoder(w io.Writer) ReflectedEncoder { + enc := json.NewEncoder(w) + // For consistency with our custom JSON encoder. + enc.SetEscapeHTML(false) + return enc +} diff --git a/vendor/go.uber.org/zap/zapcore/sampler.go b/vendor/go.uber.org/zap/zapcore/sampler.go new file mode 100644 index 0000000000..b7c093a4f2 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/sampler.go @@ -0,0 +1,229 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "sync/atomic" + "time" +) + +const ( + _numLevels = _maxLevel - _minLevel + 1 + _countersPerLevel = 4096 +) + +type counter struct { + resetAt atomic.Int64 + counter atomic.Uint64 +} + +type counters [_numLevels][_countersPerLevel]counter + +func newCounters() *counters { + return &counters{} +} + +func (cs *counters) get(lvl Level, key string) *counter { + i := lvl - _minLevel + j := fnv32a(key) % _countersPerLevel + return &cs[i][j] +} + +// fnv32a, adapted from "hash/fnv", but without a []byte(string) alloc +func fnv32a(s string) uint32 { + const ( + offset32 = 2166136261 + prime32 = 16777619 + ) + hash := uint32(offset32) + for i := 0; i < len(s); i++ { + hash ^= uint32(s[i]) + hash *= prime32 + } + return hash +} + +func (c *counter) IncCheckReset(t time.Time, tick time.Duration) uint64 { + tn := t.UnixNano() + resetAfter := c.resetAt.Load() + if resetAfter > tn { + return c.counter.Add(1) + } + + c.counter.Store(1) + + newResetAfter := tn + tick.Nanoseconds() + if !c.resetAt.CompareAndSwap(resetAfter, newResetAfter) { + // We raced with another goroutine trying to reset, and it also reset + // the counter to 1, so we need to reincrement the counter. + return c.counter.Add(1) + } + + return 1 +} + +// SamplingDecision is a decision represented as a bit field made by sampler. +// More decisions may be added in the future. +type SamplingDecision uint32 + +const ( + // LogDropped indicates that the Sampler dropped a log entry. + LogDropped SamplingDecision = 1 << iota + // LogSampled indicates that the Sampler sampled a log entry. + LogSampled +) + +// optionFunc wraps a func so it satisfies the SamplerOption interface. +type optionFunc func(*sampler) + +func (f optionFunc) apply(s *sampler) { + f(s) +} + +// SamplerOption configures a Sampler. +type SamplerOption interface { + apply(*sampler) +} + +// nopSamplingHook is the default hook used by sampler. +func nopSamplingHook(Entry, SamplingDecision) {} + +// SamplerHook registers a function which will be called when Sampler makes a +// decision. +// +// This hook may be used to get visibility into the performance of the sampler. +// For example, use it to track metrics of dropped versus sampled logs. +// +// var dropped atomic.Int64 +// zapcore.SamplerHook(func(ent zapcore.Entry, dec zapcore.SamplingDecision) { +// if dec&zapcore.LogDropped > 0 { +// dropped.Inc() +// } +// }) +func SamplerHook(hook func(entry Entry, dec SamplingDecision)) SamplerOption { + return optionFunc(func(s *sampler) { + s.hook = hook + }) +} + +// NewSamplerWithOptions creates a Core that samples incoming entries, which +// caps the CPU and I/O load of logging while attempting to preserve a +// representative subset of your logs. +// +// Zap samples by logging the first N entries with a given level and message +// each tick. If more Entries with the same level and message are seen during +// the same interval, every Mth message is logged and the rest are dropped. +// +// For example, +// +// core = NewSamplerWithOptions(core, time.Second, 10, 5) +// +// This will log the first 10 log entries with the same level and message +// in a one second interval as-is. Following that, it will allow through +// every 5th log entry with the same level and message in that interval. +// +// If thereafter is zero, the Core will drop all log entries after the first N +// in that interval. +// +// Sampler can be configured to report sampling decisions with the SamplerHook +// option. +// +// Keep in mind that Zap's sampling implementation is optimized for speed over +// absolute precision; under load, each tick may be slightly over- or +// under-sampled. +func NewSamplerWithOptions(core Core, tick time.Duration, first, thereafter int, opts ...SamplerOption) Core { + s := &sampler{ + Core: core, + tick: tick, + counts: newCounters(), + first: uint64(first), + thereafter: uint64(thereafter), + hook: nopSamplingHook, + } + for _, opt := range opts { + opt.apply(s) + } + + return s +} + +type sampler struct { + Core + + counts *counters + tick time.Duration + first, thereafter uint64 + hook func(Entry, SamplingDecision) +} + +var ( + _ Core = (*sampler)(nil) + _ leveledEnabler = (*sampler)(nil) +) + +// NewSampler creates a Core that samples incoming entries, which +// caps the CPU and I/O load of logging while attempting to preserve a +// representative subset of your logs. +// +// Zap samples by logging the first N entries with a given level and message +// each tick. If more Entries with the same level and message are seen during +// the same interval, every Mth message is logged and the rest are dropped. +// +// Keep in mind that zap's sampling implementation is optimized for speed over +// absolute precision; under load, each tick may be slightly over- or +// under-sampled. +// +// Deprecated: use NewSamplerWithOptions. +func NewSampler(core Core, tick time.Duration, first, thereafter int) Core { + return NewSamplerWithOptions(core, tick, first, thereafter) +} + +func (s *sampler) Level() Level { + return LevelOf(s.Core) +} + +func (s *sampler) With(fields []Field) Core { + return &sampler{ + Core: s.Core.With(fields), + tick: s.tick, + counts: s.counts, + first: s.first, + thereafter: s.thereafter, + hook: s.hook, + } +} + +func (s *sampler) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + if !s.Enabled(ent.Level) { + return ce + } + + if ent.Level >= _minLevel && ent.Level <= _maxLevel { + counter := s.counts.get(ent.Level, ent.Message) + n := counter.IncCheckReset(ent.Time, s.tick) + if n > s.first && (s.thereafter == 0 || (n-s.first)%s.thereafter != 0) { + s.hook(ent, LogDropped) + return ce + } + s.hook(ent, LogSampled) + } + return s.Core.Check(ent, ce) +} diff --git a/vendor/go.uber.org/zap/zapcore/sampler_bench_test.go b/vendor/go.uber.org/zap/zapcore/sampler_bench_test.go new file mode 100644 index 0000000000..100e2268e1 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/sampler_bench_test.go @@ -0,0 +1,285 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "fmt" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap/internal/ztest" + . "go.uber.org/zap/zapcore" +) + +var counterTestCases = [][]string{ + // some stuff I made up + { + "foo", + "bar", + "baz", + "alpha", + "bravo", + "charlie", + "delta", + }, + + // shuf -n50 /usr/share/dict/words + { + "unbracing", + "stereotomy", + "supranervian", + "moaning", + "exchangeability", + "gunyang", + "sulcation", + "dariole", + "archheresy", + "synchronistically", + "clips", + "unsanctioned", + "Argoan", + "liparomphalus", + "layship", + "Fregatae", + "microzoology", + "glaciaria", + "Frugivora", + "patterist", + "Grossulariaceae", + "lithotint", + "bargander", + "opisthographical", + "cacography", + "chalkstone", + "nonsubstantialism", + "sardonicism", + "calamiform", + "lodginghouse", + "predisposedly", + "topotypic", + "broideress", + "outrange", + "gingivolabial", + "monoazo", + "sparlike", + "concameration", + "untoothed", + "Camorrism", + "reissuer", + "soap", + "palaiotype", + "countercharm", + "yellowbird", + "palterly", + "writinger", + "boatfalls", + "tuglike", + "underbitten", + }, + + // shuf -n100 /usr/share/dict/words + { + "rooty", + "malcultivation", + "degrade", + "pseudoindependent", + "stillatory", + "antiseptize", + "protoamphibian", + "antiar", + "Esther", + "pseudelminth", + "superfluitance", + "teallite", + "disunity", + "spirignathous", + "vergency", + "myliobatid", + "inosic", + "overabstemious", + "patriarchally", + "foreimagine", + "coetaneity", + "hemimellitene", + "hyperspatial", + "aulophyte", + "electropoion", + "antitrope", + "Amarantus", + "smaltine", + "lighthead", + "syntonically", + "incubous", + "versation", + "cirsophthalmia", + "Ulidian", + "homoeography", + "Velella", + "Hecatean", + "serfage", + "Spermaphyta", + "palatoplasty", + "electroextraction", + "aconite", + "avirulence", + "initiator", + "besmear", + "unrecognizably", + "euphoniousness", + "balbuties", + "pascuage", + "quebracho", + "Yakala", + "auriform", + "sevenbark", + "superorganism", + "telesterion", + "ensand", + "nagaika", + "anisuria", + "etching", + "soundingly", + "grumpish", + "drillmaster", + "perfumed", + "dealkylate", + "anthracitiferous", + "predefiance", + "sulphoxylate", + "freeness", + "untucking", + "misworshiper", + "Nestorianize", + "nonegoistical", + "construe", + "upstroke", + "teated", + "nasolachrymal", + "Mastodontidae", + "gallows", + "radioluminescent", + "uncourtierlike", + "phasmatrope", + "Clunisian", + "drainage", + "sootless", + "brachyfacial", + "antiheroism", + "irreligionize", + "ked", + "unfact", + "nonprofessed", + "milady", + "conjecture", + "Arctomys", + "guapilla", + "Sassenach", + "emmetrope", + "rosewort", + "raphidiferous", + "pooh", + "Tyndallize", + }, +} + +func BenchmarkSampler_Check(b *testing.B) { + for _, keys := range counterTestCases { + b.Run(fmt.Sprintf("%v keys", len(keys)), func(b *testing.B) { + fac := NewSamplerWithOptions( + NewCore( + NewJSONEncoder(testEncoderConfig()), + &ztest.Discarder{}, + DebugLevel, + ), + time.Millisecond, 1, 1000) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + ent := Entry{ + Level: DebugLevel + Level(i%4), + Message: keys[i], + } + _ = fac.Check(ent, nil) + i++ + if n := len(keys); i >= n { + i -= n + } + } + }) + }) + } +} + +func makeSamplerCountingHook() (func(_ Entry, dec SamplingDecision), *atomic.Int64, *atomic.Int64) { + droppedCount := new(atomic.Int64) + sampledCount := new(atomic.Int64) + h := func(_ Entry, dec SamplingDecision) { + if dec&LogDropped > 0 { + droppedCount.Add(1) + } else if dec&LogSampled > 0 { + sampledCount.Add(1) + } + } + return h, droppedCount, sampledCount +} + +func BenchmarkSampler_CheckWithHook(b *testing.B) { + hook, dropped, sampled := makeSamplerCountingHook() + for _, keys := range counterTestCases { + b.Run(fmt.Sprintf("%v keys", len(keys)), func(b *testing.B) { + fac := NewSamplerWithOptions( + NewCore( + NewJSONEncoder(testEncoderConfig()), + &ztest.Discarder{}, + DebugLevel, + ), + time.Millisecond, + 1, + 1000, + SamplerHook(hook), + ) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + ent := Entry{ + Level: DebugLevel + Level(i%4), + Message: keys[i], + } + _ = fac.Check(ent, nil) + i++ + if n := len(keys); i >= n { + i -= n + } + } + }) + // We expect to see 1000 dropped messages for every sampled per settings, + // with a delta due to less 1000 messages getting dropped after initial one + // is sampled. + assert.Greater(b, dropped.Load()/1000, sampled.Load()-1000) + dropped.Store(0) + sampled.Store(0) + }) + } +} diff --git a/vendor/go.uber.org/zap/zapcore/tee.go b/vendor/go.uber.org/zap/zapcore/tee.go new file mode 100644 index 0000000000..9bb32f0557 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/tee.go @@ -0,0 +1,96 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "go.uber.org/multierr" + +type multiCore []Core + +var ( + _ leveledEnabler = multiCore(nil) + _ Core = multiCore(nil) +) + +// NewTee creates a Core that duplicates log entries into two or more +// underlying Cores. +// +// Calling it with a single Core returns the input unchanged, and calling +// it with no input returns a no-op Core. +func NewTee(cores ...Core) Core { + switch len(cores) { + case 0: + return NewNopCore() + case 1: + return cores[0] + default: + return multiCore(cores) + } +} + +func (mc multiCore) With(fields []Field) Core { + clone := make(multiCore, len(mc)) + for i := range mc { + clone[i] = mc[i].With(fields) + } + return clone +} + +func (mc multiCore) Level() Level { + minLvl := _maxLevel // mc is never empty + for i := range mc { + if lvl := LevelOf(mc[i]); lvl < minLvl { + minLvl = lvl + } + } + return minLvl +} + +func (mc multiCore) Enabled(lvl Level) bool { + for i := range mc { + if mc[i].Enabled(lvl) { + return true + } + } + return false +} + +func (mc multiCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + for i := range mc { + ce = mc[i].Check(ent, ce) + } + return ce +} + +func (mc multiCore) Write(ent Entry, fields []Field) error { + var err error + for i := range mc { + err = multierr.Append(err, mc[i].Write(ent, fields)) + } + return err +} + +func (mc multiCore) Sync() error { + var err error + for i := range mc { + err = multierr.Append(err, mc[i].Sync()) + } + return err +} diff --git a/vendor/go.uber.org/zap/zapcore/write_syncer.go b/vendor/go.uber.org/zap/zapcore/write_syncer.go new file mode 100644 index 0000000000..d4a1af3d07 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/write_syncer.go @@ -0,0 +1,122 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "io" + "sync" + + "go.uber.org/multierr" +) + +// A WriteSyncer is an io.Writer that can also flush any buffered data. Note +// that *os.File (and thus, os.Stderr and os.Stdout) implement WriteSyncer. +type WriteSyncer interface { + io.Writer + Sync() error +} + +// AddSync converts an io.Writer to a WriteSyncer. It attempts to be +// intelligent: if the concrete type of the io.Writer implements WriteSyncer, +// we'll use the existing Sync method. If it doesn't, we'll add a no-op Sync. +func AddSync(w io.Writer) WriteSyncer { + switch w := w.(type) { + case WriteSyncer: + return w + default: + return writerWrapper{w} + } +} + +type lockedWriteSyncer struct { + sync.Mutex + ws WriteSyncer +} + +// Lock wraps a WriteSyncer in a mutex to make it safe for concurrent use. In +// particular, *os.Files must be locked before use. +func Lock(ws WriteSyncer) WriteSyncer { + if _, ok := ws.(*lockedWriteSyncer); ok { + // no need to layer on another lock + return ws + } + return &lockedWriteSyncer{ws: ws} +} + +func (s *lockedWriteSyncer) Write(bs []byte) (int, error) { + s.Lock() + n, err := s.ws.Write(bs) + s.Unlock() + return n, err +} + +func (s *lockedWriteSyncer) Sync() error { + s.Lock() + err := s.ws.Sync() + s.Unlock() + return err +} + +type writerWrapper struct { + io.Writer +} + +func (w writerWrapper) Sync() error { + return nil +} + +type multiWriteSyncer []WriteSyncer + +// NewMultiWriteSyncer creates a WriteSyncer that duplicates its writes +// and sync calls, much like io.MultiWriter. +func NewMultiWriteSyncer(ws ...WriteSyncer) WriteSyncer { + if len(ws) == 1 { + return ws[0] + } + return multiWriteSyncer(ws) +} + +// See https://golang.org/src/io/multi.go +// When not all underlying syncers write the same number of bytes, +// the smallest number is returned even though Write() is called on +// all of them. +func (ws multiWriteSyncer) Write(p []byte) (int, error) { + var writeErr error + nWritten := 0 + for _, w := range ws { + n, err := w.Write(p) + writeErr = multierr.Append(writeErr, err) + if nWritten == 0 && n != 0 { + nWritten = n + } else if n < nWritten { + nWritten = n + } + } + return nWritten, writeErr +} + +func (ws multiWriteSyncer) Sync() error { + var err error + for _, w := range ws { + err = multierr.Append(err, w.Sync()) + } + return err +} diff --git a/vendor/go.uber.org/zap/zapcore/write_syncer_bench_test.go b/vendor/go.uber.org/zap/zapcore/write_syncer_bench_test.go new file mode 100644 index 0000000000..90ae475581 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/write_syncer_bench_test.go @@ -0,0 +1,101 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/internal/ztest" +) + +func BenchmarkMultiWriteSyncer(b *testing.B) { + b.Run("2 discarder", func(b *testing.B) { + w := NewMultiWriteSyncer( + &ztest.Discarder{}, + &ztest.Discarder{}, + ) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := w.Write([]byte("foobarbazbabble")); err != nil { + b.Fatal(err) + } + } + }) + }) + b.Run("4 discarder", func(b *testing.B) { + w := NewMultiWriteSyncer( + &ztest.Discarder{}, + &ztest.Discarder{}, + &ztest.Discarder{}, + &ztest.Discarder{}, + ) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := w.Write([]byte("foobarbazbabble")); err != nil { + b.Fatal(err) + } + } + }) + }) + b.Run("4 discarder with buffer", func(b *testing.B) { + w := &BufferedWriteSyncer{ + WS: NewMultiWriteSyncer( + &ztest.Discarder{}, + &ztest.Discarder{}, + &ztest.Discarder{}, + &ztest.Discarder{}, + ), + } + defer func() { + assert.NoError(b, w.Stop(), "Unexpected error stopping buffered write syncer.") + }() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := w.Write([]byte("foobarbazbabble")); err != nil { + b.Fatal(err) + } + } + }) + }) +} + +func BenchmarkWriteSyncer(b *testing.B) { + b.Run("write file with no buffer", func(b *testing.B) { + file, err := os.CreateTemp(b.TempDir(), "test.log") + require.NoError(b, err) + + w := AddSync(file) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := w.Write([]byte("foobarbazbabble")); err != nil { + b.Fatal(err) + } + } + }) + }) +} diff --git a/vendor/go.uber.org/zap/zapcore/write_syncer_test.go b/vendor/go.uber.org/zap/zapcore/write_syncer_test.go new file mode 100644 index 0000000000..c0c2698e9e --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/write_syncer_test.go @@ -0,0 +1,136 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bytes" + "errors" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/internal/ztest" +) + +type writeSyncSpy struct { + io.Writer + ztest.Syncer +} + +func requireWriteWorks(t testing.TB, ws WriteSyncer) { + n, err := ws.Write([]byte("foo")) + require.NoError(t, err, "Unexpected error writing to WriteSyncer.") + require.Equal(t, 3, n, "Wrote an unexpected number of bytes.") +} + +func TestAddSyncWriteSyncer(t *testing.T) { + buf := &bytes.Buffer{} + concrete := &writeSyncSpy{Writer: buf} + ws := AddSync(concrete) + requireWriteWorks(t, ws) + + require.NoError(t, ws.Sync(), "Unexpected error syncing a WriteSyncer.") + require.True(t, concrete.Called(), "Expected to dispatch to concrete type's Sync method.") + + concrete.SetError(errors.New("fail")) + assert.Error(t, ws.Sync(), "Expected to propagate errors from concrete type's Sync method.") +} + +func TestAddSyncWriter(t *testing.T) { + // If we pass a plain io.Writer, make sure that we still get a WriteSyncer + // with a no-op Sync. + buf := &bytes.Buffer{} + ws := AddSync(buf) + requireWriteWorks(t, ws) + assert.NoError(t, ws.Sync(), "Unexpected error calling a no-op Sync method.") +} + +func TestNewMultiWriteSyncerWorksForSingleWriter(t *testing.T) { + w := &ztest.Buffer{} + + ws := NewMultiWriteSyncer(w) + assert.Equal(t, w, ws, "Expected NewMultiWriteSyncer to return the same WriteSyncer object for a single argument.") + + assert.NoError(t, ws.Sync(), "Expected Sync to succeed.") + assert.True(t, w.Called(), "Expected Sync to be called on the created WriteSyncer") +} + +func TestMultiWriteSyncerWritesBoth(t *testing.T) { + first := &bytes.Buffer{} + second := &bytes.Buffer{} + ws := NewMultiWriteSyncer(AddSync(first), AddSync(second)) + + msg := []byte("dumbledore") + n, err := ws.Write(msg) + require.NoError(t, err, "Expected successful buffer write") + assert.Equal(t, len(msg), n) + + assert.Equal(t, msg, first.Bytes()) + assert.Equal(t, msg, second.Bytes()) +} + +func TestMultiWriteSyncerFailsWrite(t *testing.T) { + ws := NewMultiWriteSyncer(AddSync(&ztest.FailWriter{})) + _, err := ws.Write([]byte("test")) + assert.Error(t, err, "Write error should propagate") +} + +func TestMultiWriteSyncerFailsShortWrite(t *testing.T) { + ws := NewMultiWriteSyncer(AddSync(&ztest.ShortWriter{})) + n, err := ws.Write([]byte("test")) + assert.NoError(t, err, "Expected fake-success from short write") + assert.Equal(t, 3, n, "Expected byte count to return from underlying writer") +} + +func TestWritestoAllSyncs_EvenIfFirstErrors(t *testing.T) { + failer := &ztest.FailWriter{} + second := &bytes.Buffer{} + ws := NewMultiWriteSyncer(AddSync(failer), AddSync(second)) + + _, err := ws.Write([]byte("fail")) + assert.Error(t, err, "Expected error from call to a writer that failed") + assert.Equal(t, []byte("fail"), second.Bytes(), "Expected second sink to be written after first error") +} + +func TestMultiWriteSyncerSync_PropagatesErrors(t *testing.T) { + badsink := &ztest.Buffer{} + badsink.SetError(errors.New("sink is full")) + ws := NewMultiWriteSyncer(&ztest.Discarder{}, badsink) + + assert.Error(t, ws.Sync(), "Expected sync error to propagate") +} + +func TestMultiWriteSyncerSync_NoErrorsOnDiscard(t *testing.T) { + ws := NewMultiWriteSyncer(&ztest.Discarder{}) + assert.NoError(t, ws.Sync(), "Expected error-free sync to /dev/null") +} + +func TestMultiWriteSyncerSync_AllCalled(t *testing.T) { + failed, second := &ztest.Buffer{}, &ztest.Buffer{} + + failed.SetError(errors.New("disposal broken")) + ws := NewMultiWriteSyncer(failed, second) + + assert.Error(t, ws.Sync(), "Expected first sink to fail") + assert.True(t, failed.Called(), "Expected first sink to have Sync method called.") + assert.True(t, second.Called(), "Expected call to Sync even with first failure.") +} diff --git a/vendor/go.uber.org/zap/zapcore/ya.make b/vendor/go.uber.org/zap/zapcore/ya.make new file mode 100644 index 0000000000..6a3b005ee9 --- /dev/null +++ b/vendor/go.uber.org/zap/zapcore/ya.make @@ -0,0 +1,69 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + buffered_write_syncer.go + clock.go + console_encoder.go + core.go + doc.go + encoder.go + entry.go + error.go + field.go + hook.go + increase_level.go + json_encoder.go + lazy_with.go + level.go + level_strings.go + marshaler.go + memory_encoder.go + reflected_encoder.go + sampler.go + tee.go + write_syncer.go +) + +GO_TEST_SRCS( + buffered_write_syncer_bench_test.go + buffered_write_syncer_test.go + clock_test.go + entry_test.go + json_encoder_impl_test.go + leak_test.go + level_strings_test.go + level_test.go + memory_encoder_test.go + write_syncer_bench_test.go + write_syncer_test.go +) + +GO_XTEST_SRCS( + console_encoder_bench_test.go + console_encoder_test.go + core_test.go + encoder_test.go + error_test.go + # field_test.go + # hook_test.go + # increase_level_test.go + json_encoder_bench_test.go + # json_encoder_test.go + sampler_bench_test.go + # sampler_test.go + #tee_logger_bench_test.go + # tee_test.go +) + +GO_SKIP_TESTS( + entry_ext_test.go + lazy_with_test.go +) + +END() + +RECURSE( + gotest +) diff --git a/vendor/go.uber.org/zap/zapgrpc/gotest/ya.make b/vendor/go.uber.org/zap/zapgrpc/gotest/ya.make new file mode 100644 index 0000000000..4921dd5ac1 --- /dev/null +++ b/vendor/go.uber.org/zap/zapgrpc/gotest/ya.make @@ -0,0 +1,5 @@ +GO_TEST_FOR(vendor/go.uber.org/zap/zapgrpc) + +LICENSE(MIT) + +END() diff --git a/vendor/go.uber.org/zap/zapgrpc/ya.make b/vendor/go.uber.org/zap/zapgrpc/ya.make new file mode 100644 index 0000000000..cea7a6794c --- /dev/null +++ b/vendor/go.uber.org/zap/zapgrpc/ya.make @@ -0,0 +1,15 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + zapgrpc.go +) + +GO_TEST_SRCS(zapgrpc_test.go) + +END() + +RECURSE( + gotest +) diff --git a/vendor/go.uber.org/zap/zapgrpc/zapgrpc.go b/vendor/go.uber.org/zap/zapgrpc/zapgrpc.go new file mode 100644 index 0000000000..6823773b72 --- /dev/null +++ b/vendor/go.uber.org/zap/zapgrpc/zapgrpc.go @@ -0,0 +1,245 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package zapgrpc provides a logger that is compatible with grpclog. +package zapgrpc // import "go.uber.org/zap/zapgrpc" + +import ( + "fmt" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// See https://github.com/grpc/grpc-go/blob/v1.35.0/grpclog/loggerv2.go#L77-L86 +const ( + grpcLvlInfo int = iota + grpcLvlWarn + grpcLvlError + grpcLvlFatal +) + +var ( + // _grpcToZapLevel maps gRPC log levels to zap log levels. + // See https://pkg.go.dev/go.uber.org/zap@v1.16.0/zapcore#Level + _grpcToZapLevel = map[int]zapcore.Level{ + grpcLvlInfo: zapcore.InfoLevel, + grpcLvlWarn: zapcore.WarnLevel, + grpcLvlError: zapcore.ErrorLevel, + grpcLvlFatal: zapcore.FatalLevel, + } +) + +// An Option overrides a Logger's default configuration. +type Option interface { + apply(*Logger) +} + +type optionFunc func(*Logger) + +func (f optionFunc) apply(log *Logger) { + f(log) +} + +// WithDebug configures a Logger to print at zap's DebugLevel instead of +// InfoLevel. +// It only affects the Printf, Println and Print methods, which are only used in the gRPC v1 grpclog.Logger API. +// +// Deprecated: use grpclog.SetLoggerV2() for v2 API. +func WithDebug() Option { + return optionFunc(func(logger *Logger) { + logger.print = &printer{ + enab: logger.levelEnabler, + level: zapcore.DebugLevel, + print: logger.delegate.Debug, + printf: logger.delegate.Debugf, + } + }) +} + +// withWarn redirects the fatal level to the warn level, which makes testing +// easier. This is intentionally unexported. +func withWarn() Option { + return optionFunc(func(logger *Logger) { + logger.fatal = &printer{ + enab: logger.levelEnabler, + level: zapcore.WarnLevel, + print: logger.delegate.Warn, + printf: logger.delegate.Warnf, + } + }) +} + +// NewLogger returns a new Logger. +func NewLogger(l *zap.Logger, options ...Option) *Logger { + logger := &Logger{ + delegate: l.Sugar(), + levelEnabler: l.Core(), + } + logger.print = &printer{ + enab: logger.levelEnabler, + level: zapcore.InfoLevel, + print: logger.delegate.Info, + printf: logger.delegate.Infof, + } + logger.fatal = &printer{ + enab: logger.levelEnabler, + level: zapcore.FatalLevel, + print: logger.delegate.Fatal, + printf: logger.delegate.Fatalf, + } + for _, option := range options { + option.apply(logger) + } + return logger +} + +// printer implements Print, Printf, and Println operations for a Zap level. +// +// We use it to customize Debug vs Info, and Warn vs Fatal for Print and Fatal +// respectively. +type printer struct { + enab zapcore.LevelEnabler + level zapcore.Level + print func(...interface{}) + printf func(string, ...interface{}) +} + +func (v *printer) Print(args ...interface{}) { + v.print(args...) +} + +func (v *printer) Printf(format string, args ...interface{}) { + v.printf(format, args...) +} + +func (v *printer) Println(args ...interface{}) { + if v.enab.Enabled(v.level) { + v.print(sprintln(args)) + } +} + +// Logger adapts zap's Logger to be compatible with grpclog.LoggerV2 and the deprecated grpclog.Logger. +type Logger struct { + delegate *zap.SugaredLogger + levelEnabler zapcore.LevelEnabler + print *printer + fatal *printer + // printToDebug bool + // fatalToWarn bool +} + +// Print implements grpclog.Logger. +// +// Deprecated: use [Logger.Info]. +func (l *Logger) Print(args ...interface{}) { + l.print.Print(args...) +} + +// Printf implements grpclog.Logger. +// +// Deprecated: use [Logger.Infof]. +func (l *Logger) Printf(format string, args ...interface{}) { + l.print.Printf(format, args...) +} + +// Println implements grpclog.Logger. +// +// Deprecated: use [Logger.Info]. +func (l *Logger) Println(args ...interface{}) { + l.print.Println(args...) +} + +// Info implements grpclog.LoggerV2. +func (l *Logger) Info(args ...interface{}) { + l.delegate.Info(args...) +} + +// Infoln implements grpclog.LoggerV2. +func (l *Logger) Infoln(args ...interface{}) { + if l.levelEnabler.Enabled(zapcore.InfoLevel) { + l.delegate.Info(sprintln(args)) + } +} + +// Infof implements grpclog.LoggerV2. +func (l *Logger) Infof(format string, args ...interface{}) { + l.delegate.Infof(format, args...) +} + +// Warning implements grpclog.LoggerV2. +func (l *Logger) Warning(args ...interface{}) { + l.delegate.Warn(args...) +} + +// Warningln implements grpclog.LoggerV2. +func (l *Logger) Warningln(args ...interface{}) { + if l.levelEnabler.Enabled(zapcore.WarnLevel) { + l.delegate.Warn(sprintln(args)) + } +} + +// Warningf implements grpclog.LoggerV2. +func (l *Logger) Warningf(format string, args ...interface{}) { + l.delegate.Warnf(format, args...) +} + +// Error implements grpclog.LoggerV2. +func (l *Logger) Error(args ...interface{}) { + l.delegate.Error(args...) +} + +// Errorln implements grpclog.LoggerV2. +func (l *Logger) Errorln(args ...interface{}) { + if l.levelEnabler.Enabled(zapcore.ErrorLevel) { + l.delegate.Error(sprintln(args)) + } +} + +// Errorf implements grpclog.LoggerV2. +func (l *Logger) Errorf(format string, args ...interface{}) { + l.delegate.Errorf(format, args...) +} + +// Fatal implements grpclog.LoggerV2. +func (l *Logger) Fatal(args ...interface{}) { + l.fatal.Print(args...) +} + +// Fatalln implements grpclog.LoggerV2. +func (l *Logger) Fatalln(args ...interface{}) { + l.fatal.Println(args...) +} + +// Fatalf implements grpclog.LoggerV2. +func (l *Logger) Fatalf(format string, args ...interface{}) { + l.fatal.Printf(format, args...) +} + +// V implements grpclog.LoggerV2. +func (l *Logger) V(level int) bool { + return l.levelEnabler.Enabled(_grpcToZapLevel[level]) +} + +func sprintln(args []interface{}) string { + s := fmt.Sprintln(args...) + // Drop the new line character added by Sprintln + return s[:len(s)-1] +} diff --git a/vendor/go.uber.org/zap/zapgrpc/zapgrpc_test.go b/vendor/go.uber.org/zap/zapgrpc/zapgrpc_test.go new file mode 100644 index 0000000000..a231d65ec8 --- /dev/null +++ b/vendor/go.uber.org/zap/zapgrpc/zapgrpc_test.go @@ -0,0 +1,263 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapgrpc + +import ( + "fmt" + "testing" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/stretchr/testify/require" +) + +func TestLoggerInfoExpected(t *testing.T) { + checkMessages(t, zapcore.DebugLevel, nil, zapcore.InfoLevel, []string{ + "hello", + "s1s21 2 3s34s56", + "hello world", + "", + "foo", + "foo bar", + "s1 s2 1 2 3 s3 4 s5 6", + "hello", + "s1s21 2 3s34s56", + "hello world", + "", + "foo", + "foo bar", + "s1 s2 1 2 3 s3 4 s5 6", + }, func(logger *Logger) { + logger.Info("hello") + logger.Info("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + logger.Infof("%s world", "hello") + logger.Infoln() + logger.Infoln("foo") + logger.Infoln("foo", "bar") + logger.Infoln("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + logger.Print("hello") + logger.Print("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + logger.Printf("%s world", "hello") + logger.Println() + logger.Println("foo") + logger.Println("foo", "bar") + logger.Println("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + }) +} + +func TestLoggerDebugExpected(t *testing.T) { + checkMessages(t, zapcore.DebugLevel, []Option{WithDebug()}, zapcore.DebugLevel, []string{ + "hello", + "s1s21 2 3s34s56", + "hello world", + "", + "foo", + "foo bar", + "s1 s2 1 2 3 s3 4 s5 6", + }, func(logger *Logger) { + logger.Print("hello") + logger.Print("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + logger.Printf("%s world", "hello") + logger.Println() + logger.Println("foo") + logger.Println("foo", "bar") + logger.Println("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + }) +} + +func TestLoggerDebugSuppressed(t *testing.T) { + checkMessages(t, zapcore.InfoLevel, []Option{WithDebug()}, zapcore.DebugLevel, nil, func(logger *Logger) { + logger.Print("hello") + logger.Printf("%s world", "hello") + logger.Println() + logger.Println("foo") + logger.Println("foo", "bar") + }) +} + +func TestLoggerWarningExpected(t *testing.T) { + checkMessages(t, zapcore.DebugLevel, nil, zapcore.WarnLevel, []string{ + "hello", + "s1s21 2 3s34s56", + "hello world", + "", + "foo", + "foo bar", + "s1 s2 1 2 3 s3 4 s5 6", + }, func(logger *Logger) { + logger.Warning("hello") + logger.Warning("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + logger.Warningf("%s world", "hello") + logger.Warningln() + logger.Warningln("foo") + logger.Warningln("foo", "bar") + logger.Warningln("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + }) +} + +func TestLoggerErrorExpected(t *testing.T) { + checkMessages(t, zapcore.DebugLevel, nil, zapcore.ErrorLevel, []string{ + "hello", + "s1s21 2 3s34s56", + "hello world", + "", + "foo", + "foo bar", + "s1 s2 1 2 3 s3 4 s5 6", + }, func(logger *Logger) { + logger.Error("hello") + logger.Error("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + logger.Errorf("%s world", "hello") + logger.Errorln() + logger.Errorln("foo") + logger.Errorln("foo", "bar") + logger.Errorln("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + }) +} + +func TestLoggerFatalExpected(t *testing.T) { + checkMessages(t, zapcore.DebugLevel, nil, zapcore.FatalLevel, []string{ + "hello", + "s1s21 2 3s34s56", + "hello world", + "", + "foo", + "foo bar", + "s1 s2 1 2 3 s3 4 s5 6", + }, func(logger *Logger) { + logger.Fatal("hello") + logger.Fatal("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + logger.Fatalf("%s world", "hello") + logger.Fatalln() + logger.Fatalln("foo") + logger.Fatalln("foo", "bar") + logger.Fatalln("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + }) +} + +func TestLoggerV(t *testing.T) { + tests := []struct { + zapLevel zapcore.Level + grpcEnabled []int + grpcDisabled []int + }{ + { + zapLevel: zapcore.DebugLevel, + grpcEnabled: []int{grpcLvlInfo, grpcLvlWarn, grpcLvlError, grpcLvlFatal}, + grpcDisabled: []int{}, // everything is enabled, nothing is disabled + }, + { + zapLevel: zapcore.InfoLevel, + grpcEnabled: []int{grpcLvlInfo, grpcLvlWarn, grpcLvlError, grpcLvlFatal}, + grpcDisabled: []int{}, // everything is enabled, nothing is disabled + }, + { + zapLevel: zapcore.WarnLevel, + grpcEnabled: []int{grpcLvlWarn, grpcLvlError, grpcLvlFatal}, + grpcDisabled: []int{grpcLvlInfo}, + }, + { + zapLevel: zapcore.ErrorLevel, + grpcEnabled: []int{grpcLvlError, grpcLvlFatal}, + grpcDisabled: []int{grpcLvlInfo, grpcLvlWarn}, + }, + { + zapLevel: zapcore.DPanicLevel, + grpcEnabled: []int{grpcLvlFatal}, + grpcDisabled: []int{grpcLvlInfo, grpcLvlWarn, grpcLvlError}, + }, + { + zapLevel: zapcore.PanicLevel, + grpcEnabled: []int{grpcLvlFatal}, + grpcDisabled: []int{grpcLvlInfo, grpcLvlWarn, grpcLvlError}, + }, + { + zapLevel: zapcore.FatalLevel, + grpcEnabled: []int{grpcLvlFatal}, + grpcDisabled: []int{grpcLvlInfo, grpcLvlWarn, grpcLvlError}, + }, + } + for _, tst := range tests { + for _, grpcLvl := range tst.grpcEnabled { + t.Run(fmt.Sprintf("enabled %s %d", tst.zapLevel, grpcLvl), func(t *testing.T) { + checkLevel(t, tst.zapLevel, true, func(logger *Logger) bool { + return logger.V(grpcLvl) + }) + }) + } + for _, grpcLvl := range tst.grpcDisabled { + t.Run(fmt.Sprintf("disabled %s %d", tst.zapLevel, grpcLvl), func(t *testing.T) { + checkLevel(t, tst.zapLevel, false, func(logger *Logger) bool { + return logger.V(grpcLvl) + }) + }) + } + } +} + +func checkLevel( + t testing.TB, + enab zapcore.LevelEnabler, + expectedBool bool, + f func(*Logger) bool, +) { + withLogger(enab, nil, func(logger *Logger, observedLogs *observer.ObservedLogs) { + actualBool := f(logger) + if expectedBool { + require.True(t, actualBool) + } else { + require.False(t, actualBool) + } + }) +} + +func checkMessages( + t testing.TB, + enab zapcore.LevelEnabler, + opts []Option, + expectedLevel zapcore.Level, + expectedMessages []string, + f func(*Logger), +) { + if expectedLevel == zapcore.FatalLevel { + expectedLevel = zapcore.WarnLevel + } + withLogger(enab, opts, func(logger *Logger, observedLogs *observer.ObservedLogs) { + f(logger) + logEntries := observedLogs.All() + require.Equal(t, len(expectedMessages), len(logEntries)) + for i, logEntry := range logEntries { + require.Equal(t, expectedLevel, logEntry.Level) + require.Equal(t, expectedMessages[i], logEntry.Message) + } + }) +} + +func withLogger( + enab zapcore.LevelEnabler, + opts []Option, + f func(*Logger, *observer.ObservedLogs), +) { + core, observedLogs := observer.New(enab) + f(NewLogger(zap.New(core), append(opts, withWarn())...), observedLogs) +} diff --git a/vendor/go.uber.org/zap/zapio/example_test.go b/vendor/go.uber.org/zap/zapio/example_test.go new file mode 100644 index 0000000000..e9565dbd71 --- /dev/null +++ b/vendor/go.uber.org/zap/zapio/example_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapio_test + +import ( + "io" + "log" + + "go.uber.org/zap" + "go.uber.org/zap/zapio" +) + +func ExampleWriter() { + logger := zap.NewExample() + w := &zapio.Writer{Log: logger} + + io.WriteString(w, "starting up\n") + io.WriteString(w, "running\n") + io.WriteString(w, "shutting down\n") + + if err := w.Close(); err != nil { + log.Fatal(err) + } + + // Output: + // {"level":"info","msg":"starting up"} + // {"level":"info","msg":"running"} + // {"level":"info","msg":"shutting down"} +} diff --git a/vendor/go.uber.org/zap/zapio/gotest/ya.make b/vendor/go.uber.org/zap/zapio/gotest/ya.make new file mode 100644 index 0000000000..0e433fd99b --- /dev/null +++ b/vendor/go.uber.org/zap/zapio/gotest/ya.make @@ -0,0 +1,5 @@ +GO_TEST_FOR(vendor/go.uber.org/zap/zapio) + +LICENSE(MIT) + +END() diff --git a/vendor/go.uber.org/zap/zapio/writer.go b/vendor/go.uber.org/zap/zapio/writer.go new file mode 100644 index 0000000000..a87d910fad --- /dev/null +++ b/vendor/go.uber.org/zap/zapio/writer.go @@ -0,0 +1,149 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package zapio provides tools for interacting with IO streams through Zap. +package zapio + +import ( + "bytes" + "io" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// Writer is an io.Writer that writes to the provided Zap logger, splitting log +// messages on line boundaries. The Writer will buffer writes in memory until +// it encounters a newline, or the caller calls Sync or Close. +// +// Use the Writer with packages like os/exec where an io.Writer is required, +// and you want to log the output using your existing logger configuration. For +// example, +// +// writer := &zapio.Writer{Log: logger, Level: zap.DebugLevel} +// defer writer.Close() +// +// cmd := exec.CommandContext(ctx, ...) +// cmd.Stdout = writer +// cmd.Stderr = writer +// if err := cmd.Run(); err != nil { +// return err +// } +// +// Writer must be closed when finished to flush buffered data to the logger. +type Writer struct { + // Log specifies the logger to which the Writer will write messages. + // + // The Writer will panic if Log is unspecified. + Log *zap.Logger + + // Log level for the messages written to the provided logger. + // + // If unspecified, defaults to Info. + Level zapcore.Level + + buff bytes.Buffer +} + +var ( + _ zapcore.WriteSyncer = (*Writer)(nil) + _ io.Closer = (*Writer)(nil) +) + +// Write writes the provided bytes to the underlying logger at the configured +// log level and returns the length of the bytes. +// +// Write will split the input on newlines and post each line as a new log entry +// to the logger. +func (w *Writer) Write(bs []byte) (n int, err error) { + // Skip all checks if the level isn't enabled. + if !w.Log.Core().Enabled(w.Level) { + return len(bs), nil + } + + n = len(bs) + for len(bs) > 0 { + bs = w.writeLine(bs) + } + + return n, nil +} + +// writeLine writes a single line from the input, returning the remaining, +// unconsumed bytes. +func (w *Writer) writeLine(line []byte) (remaining []byte) { + idx := bytes.IndexByte(line, '\n') + if idx < 0 { + // If there are no newlines, buffer the entire string. + w.buff.Write(line) + return nil + } + + // Split on the newline, buffer and flush the left. + line, remaining = line[:idx], line[idx+1:] + + // Fast path: if we don't have a partial message from a previous write + // in the buffer, skip the buffer and log directly. + if w.buff.Len() == 0 { + w.log(line) + return + } + + w.buff.Write(line) + + // Log empty messages in the middle of the stream so that we don't lose + // information when the user writes "foo\n\nbar". + w.flush(true /* allowEmpty */) + + return remaining +} + +// Close closes the writer, flushing any buffered data in the process. +// +// Always call Close once you're done with the Writer to ensure that it flushes +// all data. +func (w *Writer) Close() error { + return w.Sync() +} + +// Sync flushes buffered data to the logger as a new log entry even if it +// doesn't contain a newline. +func (w *Writer) Sync() error { + // Don't allow empty messages on explicit Sync calls or on Close + // because we don't want an extraneous empty message at the end of the + // stream -- it's common for files to end with a newline. + w.flush(false /* allowEmpty */) + return nil +} + +// flush flushes the buffered data to the logger, allowing empty messages only +// if the bool is set. +func (w *Writer) flush(allowEmpty bool) { + if allowEmpty || w.buff.Len() > 0 { + w.log(w.buff.Bytes()) + } + w.buff.Reset() +} + +func (w *Writer) log(b []byte) { + if ce := w.Log.Check(w.Level, string(b)); ce != nil { + ce.Write() + } +} diff --git a/vendor/go.uber.org/zap/zapio/writer_test.go b/vendor/go.uber.org/zap/zapio/writer_test.go new file mode 100644 index 0000000000..9bdf3488d4 --- /dev/null +++ b/vendor/go.uber.org/zap/zapio/writer_test.go @@ -0,0 +1,248 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapio + +import ( + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" +) + +func TestWriter(t *testing.T) { + t.Parallel() + + tests := []struct { + desc string + level zapcore.Level // defaults to info + writes []string + want []zapcore.Entry + }{ + { + desc: "simple", + writes: []string{ + "foo\n", + "bar\n", + "baz\n", + }, + want: []zapcore.Entry{ + {Level: zap.InfoLevel, Message: "foo"}, + {Level: zap.InfoLevel, Message: "bar"}, + {Level: zap.InfoLevel, Message: "baz"}, + }, + }, + { + desc: "level too low", + level: zap.DebugLevel, + writes: []string{ + "foo\n", + "bar\n", + }, + want: []zapcore.Entry{}, + }, + { + desc: "multiple newlines in a message", + level: zap.WarnLevel, + writes: []string{ + "foo\nbar\n", + "baz\n", + "qux\nquux\n", + }, + want: []zapcore.Entry{ + {Level: zap.WarnLevel, Message: "foo"}, + {Level: zap.WarnLevel, Message: "bar"}, + {Level: zap.WarnLevel, Message: "baz"}, + {Level: zap.WarnLevel, Message: "qux"}, + {Level: zap.WarnLevel, Message: "quux"}, + }, + }, + { + desc: "message split across multiple writes", + level: zap.ErrorLevel, + writes: []string{ + "foo", + "bar\nbaz", + "qux", + }, + want: []zapcore.Entry{ + {Level: zap.ErrorLevel, Message: "foobar"}, + {Level: zap.ErrorLevel, Message: "bazqux"}, + }, + }, + { + desc: "blank lines in the middle", + writes: []string{ + "foo\n\nbar\nbaz", + }, + want: []zapcore.Entry{ + {Level: zap.InfoLevel, Message: "foo"}, + {Level: zap.InfoLevel, Message: ""}, + {Level: zap.InfoLevel, Message: "bar"}, + {Level: zap.InfoLevel, Message: "baz"}, + }, + }, + { + desc: "blank line at the end", + writes: []string{ + "foo\nbar\nbaz\n", + }, + want: []zapcore.Entry{ + {Level: zap.InfoLevel, Message: "foo"}, + {Level: zap.InfoLevel, Message: "bar"}, + {Level: zap.InfoLevel, Message: "baz"}, + }, + }, + { + desc: "multiple blank line at the end", + writes: []string{ + "foo\nbar\nbaz\n\n", + }, + want: []zapcore.Entry{ + {Level: zap.InfoLevel, Message: "foo"}, + {Level: zap.InfoLevel, Message: "bar"}, + {Level: zap.InfoLevel, Message: "baz"}, + {Level: zap.InfoLevel, Message: ""}, + }, + }, + } + + for _, tt := range tests { + tt := tt // for t.Parallel + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + core, observed := observer.New(zap.InfoLevel) + + w := Writer{ + Log: zap.New(core), + Level: tt.level, + } + + for _, s := range tt.writes { + _, err := io.WriteString(&w, s) + require.NoError(t, err, "Writer.Write failed.") + } + + assert.NoError(t, w.Close(), "Writer.Close failed.") + + // Turn []observer.LoggedEntry => []zapcore.Entry + got := make([]zapcore.Entry, observed.Len()) + for i, ent := range observed.AllUntimed() { + got[i] = ent.Entry + } + assert.Equal(t, tt.want, got, "Logged entries do not match.") + }) + } +} + +func TestWrite_Sync(t *testing.T) { + t.Parallel() + + core, observed := observer.New(zap.InfoLevel) + + w := Writer{ + Log: zap.New(core), + Level: zap.InfoLevel, + } + + io.WriteString(&w, "foo") + io.WriteString(&w, "bar") + + t.Run("no sync", func(t *testing.T) { + assert.Zero(t, observed.Len(), "Expected no logs yet") + }) + + t.Run("sync", func(t *testing.T) { + defer observed.TakeAll() + + require.NoError(t, w.Sync(), "Sync must not fail") + + assert.Equal(t, []observer.LoggedEntry{ + {Entry: zapcore.Entry{Message: "foobar"}, Context: []zapcore.Field{}}, + }, observed.AllUntimed(), "Log messages did not match") + }) + + t.Run("sync on empty", func(t *testing.T) { + require.NoError(t, w.Sync(), "Sync must not fail") + assert.Zero(t, observed.Len(), "Expected no logs yet") + }) +} + +func BenchmarkWriter(b *testing.B) { + tests := []struct { + name string + writes [][]byte + }{ + { + name: "single", + writes: [][]byte{ + []byte("foobar\n"), + []byte("bazqux\n"), + }, + }, + { + name: "splits", + writes: [][]byte{ + []byte("foo"), + []byte("bar\nbaz"), + []byte("qux\n"), + }, + }, + } + + writer := Writer{ + Log: zap.New(new(partiallyNopCore)), + Level: zapcore.DebugLevel, + } + + for _, tt := range tests { + b.Run(tt.name, func(b *testing.B) { + b.ResetTimer() + + for i := 0; i < b.N; i++ { + for _, bs := range tt.writes { + writer.Write(bs) + } + } + }) + } +} + +// partiallyNopCore behaves exactly like NopCore except it always returns true +// for whether the provided level is enabled, and accepts all Check requests. +// +// This lets us measure the overhead of the writer without measuring the cost +// of logging. +type partiallyNopCore struct{} + +func (*partiallyNopCore) Enabled(zapcore.Level) bool { return true } + +func (c *partiallyNopCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { + return ce.AddCore(ent, c) +} + +func (c *partiallyNopCore) With([]zapcore.Field) zapcore.Core { return c } +func (*partiallyNopCore) Write(zapcore.Entry, []zapcore.Field) error { return nil } +func (*partiallyNopCore) Sync() error { return nil } diff --git a/vendor/go.uber.org/zap/zapio/ya.make b/vendor/go.uber.org/zap/zapio/ya.make new file mode 100644 index 0000000000..02ee8449cf --- /dev/null +++ b/vendor/go.uber.org/zap/zapio/ya.make @@ -0,0 +1,17 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + writer.go +) + +GO_TEST_SRCS(writer_test.go) + +GO_XTEST_SRCS(example_test.go) + +END() + +RECURSE( + gotest +) diff --git a/vendor/go.uber.org/zap/zaptest/doc.go b/vendor/go.uber.org/zap/zaptest/doc.go new file mode 100644 index 0000000000..b377859c4a --- /dev/null +++ b/vendor/go.uber.org/zap/zaptest/doc.go @@ -0,0 +1,22 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package zaptest provides a variety of helpers for testing log output. +package zaptest // import "go.uber.org/zap/zaptest" diff --git a/vendor/go.uber.org/zap/zaptest/gotest/ya.make b/vendor/go.uber.org/zap/zaptest/gotest/ya.make new file mode 100644 index 0000000000..18e7edc92c --- /dev/null +++ b/vendor/go.uber.org/zap/zaptest/gotest/ya.make @@ -0,0 +1,5 @@ +GO_TEST_FOR(vendor/go.uber.org/zap/zaptest) + +LICENSE(MIT) + +END() diff --git a/vendor/go.uber.org/zap/zaptest/logger.go b/vendor/go.uber.org/zap/zaptest/logger.go new file mode 100644 index 0000000000..6a4a35497a --- /dev/null +++ b/vendor/go.uber.org/zap/zaptest/logger.go @@ -0,0 +1,140 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zaptest + +import ( + "bytes" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// LoggerOption configures the test logger built by NewLogger. +type LoggerOption interface { + applyLoggerOption(*loggerOptions) +} + +type loggerOptions struct { + Level zapcore.LevelEnabler + zapOptions []zap.Option +} + +type loggerOptionFunc func(*loggerOptions) + +func (f loggerOptionFunc) applyLoggerOption(opts *loggerOptions) { + f(opts) +} + +// Level controls which messages are logged by a test Logger built by +// NewLogger. +func Level(enab zapcore.LevelEnabler) LoggerOption { + return loggerOptionFunc(func(opts *loggerOptions) { + opts.Level = enab + }) +} + +// WrapOptions adds zap.Option's to a test Logger built by NewLogger. +func WrapOptions(zapOpts ...zap.Option) LoggerOption { + return loggerOptionFunc(func(opts *loggerOptions) { + opts.zapOptions = zapOpts + }) +} + +// NewLogger builds a new Logger that logs all messages to the given +// testing.TB. +// +// logger := zaptest.NewLogger(t) +// +// Use this with a *testing.T or *testing.B to get logs which get printed only +// if a test fails or if you ran go test -v. +// +// The returned logger defaults to logging debug level messages and above. +// This may be changed by passing a zaptest.Level during construction. +// +// logger := zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel)) +// +// You may also pass zap.Option's to customize test logger. +// +// logger := zaptest.NewLogger(t, zaptest.WrapOptions(zap.AddCaller())) +func NewLogger(t TestingT, opts ...LoggerOption) *zap.Logger { + cfg := loggerOptions{ + Level: zapcore.DebugLevel, + } + for _, o := range opts { + o.applyLoggerOption(&cfg) + } + + writer := newTestingWriter(t) + zapOptions := []zap.Option{ + // Send zap errors to the same writer and mark the test as failed if + // that happens. + zap.ErrorOutput(writer.WithMarkFailed(true)), + } + zapOptions = append(zapOptions, cfg.zapOptions...) + + return zap.New( + zapcore.NewCore( + zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), + writer, + cfg.Level, + ), + zapOptions..., + ) +} + +// testingWriter is a WriteSyncer that writes to the given testing.TB. +type testingWriter struct { + t TestingT + + // If true, the test will be marked as failed if this testingWriter is + // ever used. + markFailed bool +} + +func newTestingWriter(t TestingT) testingWriter { + return testingWriter{t: t} +} + +// WithMarkFailed returns a copy of this testingWriter with markFailed set to +// the provided value. +func (w testingWriter) WithMarkFailed(v bool) testingWriter { + w.markFailed = v + return w +} + +func (w testingWriter) Write(p []byte) (n int, err error) { + n = len(p) + + // Strip trailing newline because t.Log always adds one. + p = bytes.TrimRight(p, "\n") + + // Note: t.Log is safe for concurrent use. + w.t.Logf("%s", p) + if w.markFailed { + w.t.Fail() + } + + return n, nil +} + +func (w testingWriter) Sync() error { + return nil +} diff --git a/vendor/go.uber.org/zap/zaptest/logger_test.go b/vendor/go.uber.org/zap/zaptest/logger_test.go new file mode 100644 index 0000000000..576f6828cd --- /dev/null +++ b/vendor/go.uber.org/zap/zaptest/logger_test.go @@ -0,0 +1,193 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zaptest + +import ( + "errors" + "fmt" + "io" + "strings" + "testing" + + "go.uber.org/zap" + "go.uber.org/zap/internal/ztest" + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" +) + +func TestTestLogger(t *testing.T) { + ts := newTestLogSpy(t) + defer ts.AssertPassed() + + log := NewLogger(ts) + + log.Info("received work order") + log.Debug("starting work") + log.Warn("work may fail") + log.Error("work failed", zap.Error(errors.New("great sadness"))) + + assert.Panics(t, func() { + log.Panic("failed to do work") + }, "log.Panic should panic") + + ts.AssertMessages( + "INFO received work order", + "DEBUG starting work", + "WARN work may fail", + `ERROR work failed {"error": "great sadness"}`, + "PANIC failed to do work", + ) +} + +func TestTestLoggerSupportsLevels(t *testing.T) { + ts := newTestLogSpy(t) + defer ts.AssertPassed() + + log := NewLogger(ts, Level(zap.WarnLevel)) + + log.Info("received work order") + log.Debug("starting work") + log.Warn("work may fail") + log.Error("work failed", zap.Error(errors.New("great sadness"))) + + assert.Panics(t, func() { + log.Panic("failed to do work") + }, "log.Panic should panic") + + ts.AssertMessages( + "WARN work may fail", + `ERROR work failed {"error": "great sadness"}`, + "PANIC failed to do work", + ) +} + +func TestTestLoggerSupportsWrappedZapOptions(t *testing.T) { + ts := newTestLogSpy(t) + defer ts.AssertPassed() + + log := NewLogger(ts, WrapOptions(zap.AddCaller(), zap.Fields(zap.String("k1", "v1")))) + + log.Info("received work order") + log.Debug("starting work") + log.Warn("work may fail") + log.Error("work failed", zap.Error(errors.New("great sadness"))) + + assert.Panics(t, func() { + log.Panic("failed to do work") + }, "log.Panic should panic") + + ts.AssertMessages( + `INFO zaptest/logger_test.go:89 received work order {"k1": "v1"}`, + `DEBUG zaptest/logger_test.go:90 starting work {"k1": "v1"}`, + `WARN zaptest/logger_test.go:91 work may fail {"k1": "v1"}`, + `ERROR zaptest/logger_test.go:92 work failed {"k1": "v1", "error": "great sadness"}`, + `PANIC zaptest/logger_test.go:95 failed to do work {"k1": "v1"}`, + ) +} + +func TestTestingWriter(t *testing.T) { + ts := newTestLogSpy(t) + w := newTestingWriter(ts) + + n, err := io.WriteString(w, "hello\n\n") + assert.NoError(t, err, "WriteString must not fail") + assert.Equal(t, 7, n) +} + +func TestTestLoggerErrorOutput(t *testing.T) { + // This test verifies that the test logger logs internal messages to the + // testing.T and marks the test as failed. + + ts := newTestLogSpy(t) + defer ts.AssertFailed() + + log := NewLogger(ts) + + // Replace with a core that fails. + log = log.WithOptions(zap.WrapCore(func(zapcore.Core) zapcore.Core { + return zapcore.NewCore( + zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), + zapcore.Lock(zapcore.AddSync(ztest.FailWriter{})), + zapcore.DebugLevel, + ) + })) + + log.Info("foo") // this fails + + if assert.Len(t, ts.Messages, 1, "expected a log message") { + assert.Regexp(t, `write error: failed`, ts.Messages[0]) + } +} + +// testLogSpy is a testing.TB that captures logged messages. +type testLogSpy struct { + testing.TB + + failed bool + Messages []string +} + +func newTestLogSpy(t testing.TB) *testLogSpy { + return &testLogSpy{TB: t} +} + +func (t *testLogSpy) Fail() { + t.failed = true +} + +func (t *testLogSpy) Failed() bool { + return t.failed +} + +func (t *testLogSpy) FailNow() { + t.Fail() + t.TB.FailNow() +} + +func (t *testLogSpy) Logf(format string, args ...interface{}) { + // Log messages are in the format, + // + // 2017-10-27T13:03:01.000-0700 DEBUG your message here {data here} + // + // We strip the first part of these messages because we can't really test + // for the timestamp from these tests. + m := fmt.Sprintf(format, args...) + m = m[strings.IndexByte(m, '\t')+1:] + t.Messages = append(t.Messages, m) + t.TB.Log(m) +} + +func (t *testLogSpy) AssertMessages(msgs ...string) { + assert.Equal(t.TB, msgs, t.Messages, "logged messages did not match") +} + +func (t *testLogSpy) AssertPassed() { + t.assertFailed(false, "expected test to pass") +} + +func (t *testLogSpy) AssertFailed() { + t.assertFailed(true, "expected test to fail") +} + +func (t *testLogSpy) assertFailed(v bool, msg string) { + assert.Equal(t.TB, v, t.failed, msg) +} diff --git a/vendor/go.uber.org/zap/zaptest/observer/gotest/ya.make b/vendor/go.uber.org/zap/zaptest/observer/gotest/ya.make new file mode 100644 index 0000000000..512a2db69d --- /dev/null +++ b/vendor/go.uber.org/zap/zaptest/observer/gotest/ya.make @@ -0,0 +1,5 @@ +GO_TEST_FOR(vendor/go.uber.org/zap/zaptest/observer) + +LICENSE(MIT) + +END() diff --git a/vendor/go.uber.org/zap/zaptest/observer/logged_entry.go b/vendor/go.uber.org/zap/zaptest/observer/logged_entry.go new file mode 100644 index 0000000000..a4ea7ec36c --- /dev/null +++ b/vendor/go.uber.org/zap/zaptest/observer/logged_entry.go @@ -0,0 +1,39 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package observer + +import "go.uber.org/zap/zapcore" + +// An LoggedEntry is an encoding-agnostic representation of a log message. +// Field availability is context dependant. +type LoggedEntry struct { + zapcore.Entry + Context []zapcore.Field +} + +// ContextMap returns a map for all fields in Context. +func (e LoggedEntry) ContextMap() map[string]interface{} { + encoder := zapcore.NewMapObjectEncoder() + for _, f := range e.Context { + f.AddTo(encoder) + } + return encoder.Fields +} diff --git a/vendor/go.uber.org/zap/zaptest/observer/logged_entry_test.go b/vendor/go.uber.org/zap/zaptest/observer/logged_entry_test.go new file mode 100644 index 0000000000..50f6123bdc --- /dev/null +++ b/vendor/go.uber.org/zap/zaptest/observer/logged_entry_test.go @@ -0,0 +1,88 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package observer + +import ( + "testing" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" +) + +func TestLoggedEntryContextMap(t *testing.T) { + tests := []struct { + msg string + fields []zapcore.Field + want map[string]interface{} + }{ + { + msg: "no fields", + fields: nil, + want: map[string]interface{}{}, + }, + { + msg: "simple", + fields: []zapcore.Field{ + zap.String("k1", "v"), + zap.Int64("k2", 10), + }, + want: map[string]interface{}{ + "k1": "v", + "k2": int64(10), + }, + }, + { + msg: "overwrite", + fields: []zapcore.Field{ + zap.String("k1", "v1"), + zap.String("k1", "v2"), + }, + want: map[string]interface{}{ + "k1": "v2", + }, + }, + { + msg: "nested", + fields: []zapcore.Field{ + zap.String("k1", "v1"), + zap.Namespace("nested"), + zap.String("k2", "v2"), + }, + want: map[string]interface{}{ + "k1": "v1", + "nested": map[string]interface{}{ + "k2": "v2", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.msg, func(t *testing.T) { + entry := LoggedEntry{ + Context: tt.fields, + } + assert.Equal(t, tt.want, entry.ContextMap()) + }) + } +} diff --git a/vendor/go.uber.org/zap/zaptest/observer/observer.go b/vendor/go.uber.org/zap/zaptest/observer/observer.go new file mode 100644 index 0000000000..f77f1308ba --- /dev/null +++ b/vendor/go.uber.org/zap/zaptest/observer/observer.go @@ -0,0 +1,196 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package observer provides a zapcore.Core that keeps an in-memory, +// encoding-agnostic representation of log entries. It's useful for +// applications that want to unit test their log output without tying their +// tests to a particular output encoding. +package observer // import "go.uber.org/zap/zaptest/observer" + +import ( + "strings" + "sync" + "time" + + "go.uber.org/zap/internal" + "go.uber.org/zap/zapcore" +) + +// ObservedLogs is a concurrency-safe, ordered collection of observed logs. +type ObservedLogs struct { + mu sync.RWMutex + logs []LoggedEntry +} + +// Len returns the number of items in the collection. +func (o *ObservedLogs) Len() int { + o.mu.RLock() + n := len(o.logs) + o.mu.RUnlock() + return n +} + +// All returns a copy of all the observed logs. +func (o *ObservedLogs) All() []LoggedEntry { + o.mu.RLock() + ret := make([]LoggedEntry, len(o.logs)) + copy(ret, o.logs) + o.mu.RUnlock() + return ret +} + +// TakeAll returns a copy of all the observed logs, and truncates the observed +// slice. +func (o *ObservedLogs) TakeAll() []LoggedEntry { + o.mu.Lock() + ret := o.logs + o.logs = nil + o.mu.Unlock() + return ret +} + +// AllUntimed returns a copy of all the observed logs, but overwrites the +// observed timestamps with time.Time's zero value. This is useful when making +// assertions in tests. +func (o *ObservedLogs) AllUntimed() []LoggedEntry { + ret := o.All() + for i := range ret { + ret[i].Time = time.Time{} + } + return ret +} + +// FilterLevelExact filters entries to those logged at exactly the given level. +func (o *ObservedLogs) FilterLevelExact(level zapcore.Level) *ObservedLogs { + return o.Filter(func(e LoggedEntry) bool { + return e.Level == level + }) +} + +// FilterMessage filters entries to those that have the specified message. +func (o *ObservedLogs) FilterMessage(msg string) *ObservedLogs { + return o.Filter(func(e LoggedEntry) bool { + return e.Message == msg + }) +} + +// FilterMessageSnippet filters entries to those that have a message containing the specified snippet. +func (o *ObservedLogs) FilterMessageSnippet(snippet string) *ObservedLogs { + return o.Filter(func(e LoggedEntry) bool { + return strings.Contains(e.Message, snippet) + }) +} + +// FilterField filters entries to those that have the specified field. +func (o *ObservedLogs) FilterField(field zapcore.Field) *ObservedLogs { + return o.Filter(func(e LoggedEntry) bool { + for _, ctxField := range e.Context { + if ctxField.Equals(field) { + return true + } + } + return false + }) +} + +// FilterFieldKey filters entries to those that have the specified key. +func (o *ObservedLogs) FilterFieldKey(key string) *ObservedLogs { + return o.Filter(func(e LoggedEntry) bool { + for _, ctxField := range e.Context { + if ctxField.Key == key { + return true + } + } + return false + }) +} + +// Filter returns a copy of this ObservedLogs containing only those entries +// for which the provided function returns true. +func (o *ObservedLogs) Filter(keep func(LoggedEntry) bool) *ObservedLogs { + o.mu.RLock() + defer o.mu.RUnlock() + + var filtered []LoggedEntry + for _, entry := range o.logs { + if keep(entry) { + filtered = append(filtered, entry) + } + } + return &ObservedLogs{logs: filtered} +} + +func (o *ObservedLogs) add(log LoggedEntry) { + o.mu.Lock() + o.logs = append(o.logs, log) + o.mu.Unlock() +} + +// New creates a new Core that buffers logs in memory (without any encoding). +// It's particularly useful in tests. +func New(enab zapcore.LevelEnabler) (zapcore.Core, *ObservedLogs) { + ol := &ObservedLogs{} + return &contextObserver{ + LevelEnabler: enab, + logs: ol, + }, ol +} + +type contextObserver struct { + zapcore.LevelEnabler + logs *ObservedLogs + context []zapcore.Field +} + +var ( + _ zapcore.Core = (*contextObserver)(nil) + _ internal.LeveledEnabler = (*contextObserver)(nil) +) + +func (co *contextObserver) Level() zapcore.Level { + return zapcore.LevelOf(co.LevelEnabler) +} + +func (co *contextObserver) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { + if co.Enabled(ent.Level) { + return ce.AddCore(ent, co) + } + return ce +} + +func (co *contextObserver) With(fields []zapcore.Field) zapcore.Core { + return &contextObserver{ + LevelEnabler: co.LevelEnabler, + logs: co.logs, + context: append(co.context[:len(co.context):len(co.context)], fields...), + } +} + +func (co *contextObserver) Write(ent zapcore.Entry, fields []zapcore.Field) error { + all := make([]zapcore.Field, 0, len(fields)+len(co.context)) + all = append(all, co.context...) + all = append(all, fields...) + co.logs.add(LoggedEntry{ent, all}) + return nil +} + +func (co *contextObserver) Sync() error { + return nil +} diff --git a/vendor/go.uber.org/zap/zaptest/observer/observer_test.go b/vendor/go.uber.org/zap/zaptest/observer/observer_test.go new file mode 100644 index 0000000000..0a57a0f32a --- /dev/null +++ b/vendor/go.uber.org/zap/zaptest/observer/observer_test.go @@ -0,0 +1,258 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package observer_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + . "go.uber.org/zap/zaptest/observer" +) + +func assertEmpty(t testing.TB, logs *ObservedLogs) { + assert.Equal(t, 0, logs.Len(), "Expected empty ObservedLogs to have zero length.") + assert.Equal(t, []LoggedEntry{}, logs.All(), "Unexpected LoggedEntries in empty ObservedLogs.") +} + +func TestObserver(t *testing.T) { + observer, logs := New(zap.InfoLevel) + assertEmpty(t, logs) + + t.Run("LevelOf", func(t *testing.T) { + assert.Equal(t, zap.InfoLevel, zapcore.LevelOf(observer), "Observer reported the wrong log level.") + }) + + assert.NoError(t, observer.Sync(), "Unexpected failure in no-op Sync") + + obs := zap.New(observer).With(zap.Int("i", 1)) + obs.Info("foo") + obs.Debug("bar") + want := []LoggedEntry{{ + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "foo"}, + Context: []zapcore.Field{zap.Int("i", 1)}, + }} + + assert.Equal(t, 1, logs.Len(), "Unexpected observed logs Len.") + assert.Equal(t, want, logs.AllUntimed(), "Unexpected contents from AllUntimed.") + + all := logs.All() + require.Equal(t, 1, len(all), "Unexpected number of LoggedEntries returned from All.") + assert.NotEqual(t, time.Time{}, all[0].Time, "Expected non-zero time on LoggedEntry.") + + // copy & zero time for stable assertions + untimed := append([]LoggedEntry{}, all...) + untimed[0].Time = time.Time{} + assert.Equal(t, want, untimed, "Unexpected LoggedEntries from All.") + + assert.Equal(t, all, logs.TakeAll(), "Expected All and TakeAll to return identical results.") + assertEmpty(t, logs) +} + +func TestObserverWith(t *testing.T) { + sf1, logs := New(zap.InfoLevel) + + // need to pad out enough initial fields so that the underlying slice cap() + // gets ahead of its len() so that the sf3/4 With append's could choose + // not to copy (if the implementation doesn't force them) + sf1 = sf1.With([]zapcore.Field{zap.Int("a", 1), zap.Int("b", 2)}) + + sf2 := sf1.With([]zapcore.Field{zap.Int("c", 3)}) + sf3 := sf2.With([]zapcore.Field{zap.Int("d", 4)}) + sf4 := sf2.With([]zapcore.Field{zap.Int("e", 5)}) + ent := zapcore.Entry{Level: zap.InfoLevel, Message: "hello"} + + for i, core := range []zapcore.Core{sf2, sf3, sf4} { + if ce := core.Check(ent, nil); ce != nil { + ce.Write(zap.Int("i", i)) + } + } + + assert.Equal(t, []LoggedEntry{ + { + Entry: ent, + Context: []zapcore.Field{ + zap.Int("a", 1), + zap.Int("b", 2), + zap.Int("c", 3), + zap.Int("i", 0), + }, + }, + { + Entry: ent, + Context: []zapcore.Field{ + zap.Int("a", 1), + zap.Int("b", 2), + zap.Int("c", 3), + zap.Int("d", 4), + zap.Int("i", 1), + }, + }, + { + Entry: ent, + Context: []zapcore.Field{ + zap.Int("a", 1), + zap.Int("b", 2), + zap.Int("c", 3), + zap.Int("e", 5), + zap.Int("i", 2), + }, + }, + }, logs.All(), "expected no field sharing between With siblings") +} + +func TestFilters(t *testing.T) { + logs := []LoggedEntry{ + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "log a"}, + Context: []zapcore.Field{zap.String("fStr", "1"), zap.Int("a", 1)}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "log a"}, + Context: []zapcore.Field{zap.String("fStr", "2"), zap.Int("b", 2)}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "log b"}, + Context: []zapcore.Field{zap.Int("a", 1), zap.Int("b", 2)}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "log c"}, + Context: []zapcore.Field{zap.Int("a", 1), zap.Namespace("ns"), zap.Int("a", 2)}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "msg 1"}, + Context: []zapcore.Field{zap.Int("a", 1), zap.Namespace("ns")}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "any map"}, + Context: []zapcore.Field{zap.Any("map", map[string]string{"a": "b"})}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "any slice"}, + Context: []zapcore.Field{zap.Any("slice", []string{"a"})}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "msg 2"}, + Context: []zapcore.Field{zap.Int("b", 2), zap.Namespace("filterMe")}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "any slice"}, + Context: []zapcore.Field{zap.Any("filterMe", []string{"b"})}, + }, + { + Entry: zapcore.Entry{Level: zap.WarnLevel, Message: "danger will robinson"}, + Context: []zapcore.Field{zap.Int("b", 42)}, + }, + { + Entry: zapcore.Entry{Level: zap.ErrorLevel, Message: "warp core breach"}, + Context: []zapcore.Field{zap.Int("b", 42)}, + }, + } + + logger, sink := New(zap.InfoLevel) + for _, log := range logs { + assert.NoError(t, logger.Write(log.Entry, log.Context), "Unexpected error writing log entry.") + } + + tests := []struct { + msg string + filtered *ObservedLogs + want []LoggedEntry + }{ + { + msg: "filter by message", + filtered: sink.FilterMessage("log a"), + want: logs[0:2], + }, + { + msg: "filter by field", + filtered: sink.FilterField(zap.String("fStr", "1")), + want: logs[0:1], + }, + { + msg: "filter by message and field", + filtered: sink.FilterMessage("log a").FilterField(zap.Int("b", 2)), + want: logs[1:2], + }, + { + msg: "filter by field with duplicate fields", + filtered: sink.FilterField(zap.Int("a", 2)), + want: logs[3:4], + }, + { + msg: "filter doesn't match any messages", + filtered: sink.FilterMessage("no match"), + want: []LoggedEntry{}, + }, + { + msg: "filter by snippet", + filtered: sink.FilterMessageSnippet("log"), + want: logs[0:4], + }, + { + msg: "filter by snippet and field", + filtered: sink.FilterMessageSnippet("a").FilterField(zap.Int("b", 2)), + want: logs[1:2], + }, + { + msg: "filter for map", + filtered: sink.FilterField(zap.Any("map", map[string]string{"a": "b"})), + want: logs[5:6], + }, + { + msg: "filter for slice", + filtered: sink.FilterField(zap.Any("slice", []string{"a"})), + want: logs[6:7], + }, + { + msg: "filter field key", + filtered: sink.FilterFieldKey("filterMe"), + want: logs[7:9], + }, + { + msg: "filter by arbitrary function", + filtered: sink.Filter(func(e LoggedEntry) bool { + return len(e.Context) > 1 + }), + want: func() []LoggedEntry { + // Do not modify logs slice. + w := make([]LoggedEntry, 0, len(logs)) + w = append(w, logs[0:5]...) + w = append(w, logs[7]) + return w + }(), + }, + { + msg: "filter level", + filtered: sink.FilterLevelExact(zap.WarnLevel), + want: logs[9:10], + }, + } + + for _, tt := range tests { + got := tt.filtered.AllUntimed() + assert.Equal(t, tt.want, got, tt.msg) + } +} diff --git a/vendor/go.uber.org/zap/zaptest/observer/ya.make b/vendor/go.uber.org/zap/zaptest/observer/ya.make new file mode 100644 index 0000000000..c3ae8b0a6b --- /dev/null +++ b/vendor/go.uber.org/zap/zaptest/observer/ya.make @@ -0,0 +1,18 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + logged_entry.go + observer.go +) + +GO_TEST_SRCS(logged_entry_test.go) + +GO_XTEST_SRCS(observer_test.go) + +END() + +RECURSE( + gotest +) diff --git a/vendor/go.uber.org/zap/zaptest/testingt.go b/vendor/go.uber.org/zap/zaptest/testingt.go new file mode 100644 index 0000000000..792463be30 --- /dev/null +++ b/vendor/go.uber.org/zap/zaptest/testingt.go @@ -0,0 +1,47 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zaptest + +// TestingT is a subset of the API provided by all *testing.T and *testing.B +// objects. +type TestingT interface { + // Logs the given message without failing the test. + Logf(string, ...interface{}) + + // Logs the given message and marks the test as failed. + Errorf(string, ...interface{}) + + // Marks the test as failed. + Fail() + + // Returns true if the test has been marked as failed. + Failed() bool + + // Returns the name of the test. + Name() string + + // Marks the test as failed and stops execution of that test. + FailNow() +} + +// Note: We currently only rely on Logf. We are including Errorf and FailNow +// in the interface in anticipation of future need since we can't extend the +// interface without a breaking change. diff --git a/vendor/go.uber.org/zap/zaptest/testingt_test.go b/vendor/go.uber.org/zap/zaptest/testingt_test.go new file mode 100644 index 0000000000..d8477964d0 --- /dev/null +++ b/vendor/go.uber.org/zap/zaptest/testingt_test.go @@ -0,0 +1,29 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zaptest + +import "testing" + +// Just a compile-time test to ensure that TestingT matches the testing.TB +// interface. We could do this in testingt.go but that would put a dependency +// on the "testing" package from zaptest. + +var _ TestingT = (testing.TB)(nil) diff --git a/vendor/go.uber.org/zap/zaptest/timeout.go b/vendor/go.uber.org/zap/zaptest/timeout.go new file mode 100644 index 0000000000..f0be444165 --- /dev/null +++ b/vendor/go.uber.org/zap/zaptest/timeout.go @@ -0,0 +1,45 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zaptest + +import ( + "time" + + "go.uber.org/zap/internal/ztest" +) + +// Timeout scales the provided duration by $TEST_TIMEOUT_SCALE. +// +// Deprecated: This function is intended for internal testing and shouldn't be +// used outside zap itself. It was introduced before Go supported internal +// packages. +func Timeout(base time.Duration) time.Duration { + return ztest.Timeout(base) +} + +// Sleep scales the sleep duration by $TEST_TIMEOUT_SCALE. +// +// Deprecated: This function is intended for internal testing and shouldn't be +// used outside zap itself. It was introduced before Go supported internal +// packages. +func Sleep(base time.Duration) { + ztest.Sleep(base) +} diff --git a/vendor/go.uber.org/zap/zaptest/timeout_test.go b/vendor/go.uber.org/zap/zaptest/timeout_test.go new file mode 100644 index 0000000000..3962ecdaff --- /dev/null +++ b/vendor/go.uber.org/zap/zaptest/timeout_test.go @@ -0,0 +1,43 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zaptest + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap/internal/ztest" +) + +func TestTimeout(t *testing.T) { + defer ztest.Initialize("2")() + assert.Equal(t, time.Duration(100), Timeout(50), "Expected to scale up timeout.") +} + +func TestSleep(t *testing.T) { + defer ztest.Initialize("2")() + const sleepFor = 50 * time.Millisecond + now := time.Now() + Sleep(sleepFor) + elapsed := time.Since(now) + assert.True(t, 2*sleepFor <= elapsed, "Expected to scale up timeout.") +} diff --git a/vendor/go.uber.org/zap/zaptest/writer.go b/vendor/go.uber.org/zap/zaptest/writer.go new file mode 100644 index 0000000000..4b772f8c28 --- /dev/null +++ b/vendor/go.uber.org/zap/zaptest/writer.go @@ -0,0 +1,44 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zaptest + +import "go.uber.org/zap/internal/ztest" + +type ( + // A Syncer is a spy for the Sync portion of zapcore.WriteSyncer. + Syncer = ztest.Syncer + + // A Discarder sends all writes to io.Discard. + Discarder = ztest.Discarder + + // FailWriter is a WriteSyncer that always returns an error on writes. + FailWriter = ztest.FailWriter + + // ShortWriter is a WriteSyncer whose write method never returns an error, + // but always reports that it wrote one byte less than the input slice's + // length (thus, a "short write"). + ShortWriter = ztest.ShortWriter + + // Buffer is an implementation of zapcore.WriteSyncer that sends all writes to + // a bytes.Buffer. It has convenience methods to split the accumulated buffer + // on newlines. + Buffer = ztest.Buffer +) diff --git a/vendor/go.uber.org/zap/zaptest/writer_test.go b/vendor/go.uber.org/zap/zaptest/writer_test.go new file mode 100644 index 0000000000..c18f18a359 --- /dev/null +++ b/vendor/go.uber.org/zap/zaptest/writer_test.go @@ -0,0 +1,68 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zaptest + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSyncer(t *testing.T) { + err := errors.New("sentinel") + s := &Syncer{} + s.SetError(err) + assert.Equal(t, err, s.Sync(), "Expected Sync to fail with provided error.") + assert.True(t, s.Called(), "Expected to record that Sync was called.") +} + +func TestDiscarder(t *testing.T) { + d := &Discarder{} + payload := []byte("foo") + n, err := d.Write(payload) + assert.NoError(t, err, "Unexpected error writing to Discarder.") + assert.Equal(t, len(payload), n, "Wrong number of bytes written.") +} + +func TestFailWriter(t *testing.T) { + w := &FailWriter{} + payload := []byte("foo") + n, err := w.Write(payload) + assert.Error(t, err, "Expected an error writing to FailWriter.") + assert.Equal(t, len(payload), n, "Wrong number of bytes written.") +} + +func TestShortWriter(t *testing.T) { + w := &ShortWriter{} + payload := []byte("foo") + n, err := w.Write(payload) + assert.NoError(t, err, "Unexpected error writing to ShortWriter.") + assert.Equal(t, len(payload)-1, n, "Wrong number of bytes written.") +} + +func TestBuffer(t *testing.T) { + buf := &Buffer{} + buf.WriteString("foo\n") + buf.WriteString("bar\n") + assert.Equal(t, []string{"foo", "bar"}, buf.Lines(), "Unexpected output from Lines.") + assert.Equal(t, "foo\nbar", buf.Stripped(), "Unexpected output from Stripped.") +} diff --git a/vendor/go.uber.org/zap/zaptest/ya.make b/vendor/go.uber.org/zap/zaptest/ya.make new file mode 100644 index 0000000000..4e9d3ad95f --- /dev/null +++ b/vendor/go.uber.org/zap/zaptest/ya.make @@ -0,0 +1,25 @@ +GO_LIBRARY() + +LICENSE(MIT) + +SRCS( + doc.go + logger.go + testingt.go + timeout.go + writer.go +) + +GO_TEST_SRCS( + logger_test.go + testingt_test.go + timeout_test.go + writer_test.go +) + +END() + +RECURSE( + gotest + observer +) |