updated vendor
This commit is contained in:
+131
-111
@@ -20,7 +20,9 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
@@ -51,139 +53,157 @@ func WithValues(oldKV, newKV []interface{}) []interface{} {
|
||||
return kv
|
||||
}
|
||||
|
||||
// MergeKVs deduplicates elements provided in two key/value slices.
|
||||
//
|
||||
// Keys in each slice are expected to be unique, so duplicates can only occur
|
||||
// when the first and second slice contain the same key. When that happens, the
|
||||
// key/value pair from the second slice is used. The first slice must be well-formed
|
||||
// (= even key/value pairs). The second one may have a missing value, in which
|
||||
// case the special "missing value" is added to the result.
|
||||
func MergeKVs(first, second []interface{}) []interface{} {
|
||||
maxLength := len(first) + (len(second)+1)/2*2
|
||||
if maxLength == 0 {
|
||||
// Nothing to do at all.
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(first) == 0 && len(second)%2 == 0 {
|
||||
// Nothing to be overridden, second slice is well-formed
|
||||
// and can be used directly.
|
||||
return second
|
||||
}
|
||||
|
||||
// Determine which keys are in the second slice so that we can skip
|
||||
// them when iterating over the first one. The code intentionally
|
||||
// favors performance over completeness: we assume that keys are string
|
||||
// constants and thus compare equal when the string values are equal. A
|
||||
// string constant being overridden by, for example, a fmt.Stringer is
|
||||
// not handled.
|
||||
overrides := map[interface{}]bool{}
|
||||
for i := 0; i < len(second); i += 2 {
|
||||
overrides[second[i]] = true
|
||||
}
|
||||
merged := make([]interface{}, 0, maxLength)
|
||||
for i := 0; i+1 < len(first); i += 2 {
|
||||
key := first[i]
|
||||
if overrides[key] {
|
||||
continue
|
||||
}
|
||||
merged = append(merged, key, first[i+1])
|
||||
}
|
||||
merged = append(merged, second...)
|
||||
if len(merged)%2 != 0 {
|
||||
merged = append(merged, missingValue)
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
type Formatter struct {
|
||||
AnyToStringHook AnyToStringFunc
|
||||
}
|
||||
|
||||
type AnyToStringFunc func(v interface{}) string
|
||||
|
||||
// MergeKVsInto is a variant of MergeKVs which directly formats the key/value
|
||||
// pairs into a buffer.
|
||||
func (f Formatter) MergeAndFormatKVs(b *bytes.Buffer, first, second []interface{}) {
|
||||
if len(first) == 0 && len(second) == 0 {
|
||||
// Nothing to do at all.
|
||||
return
|
||||
}
|
||||
|
||||
if len(first) == 0 && len(second)%2 == 0 {
|
||||
// Nothing to be overridden, second slice is well-formed
|
||||
// and can be used directly.
|
||||
for i := 0; i < len(second); i += 2 {
|
||||
f.KVFormat(b, second[i], second[i+1])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Determine which keys are in the second slice so that we can skip
|
||||
// them when iterating over the first one. The code intentionally
|
||||
// favors performance over completeness: we assume that keys are string
|
||||
// constants and thus compare equal when the string values are equal. A
|
||||
// string constant being overridden by, for example, a fmt.Stringer is
|
||||
// not handled.
|
||||
overrides := map[interface{}]bool{}
|
||||
for i := 0; i < len(second); i += 2 {
|
||||
overrides[second[i]] = true
|
||||
}
|
||||
for i := 0; i < len(first); i += 2 {
|
||||
key := first[i]
|
||||
if overrides[key] {
|
||||
continue
|
||||
}
|
||||
f.KVFormat(b, key, first[i+1])
|
||||
}
|
||||
// Round down.
|
||||
l := len(second)
|
||||
l = l / 2 * 2
|
||||
for i := 1; i < l; i += 2 {
|
||||
f.KVFormat(b, second[i-1], second[i])
|
||||
}
|
||||
if len(second)%2 == 1 {
|
||||
f.KVFormat(b, second[len(second)-1], missingValue)
|
||||
}
|
||||
}
|
||||
|
||||
func MergeAndFormatKVs(b *bytes.Buffer, first, second []interface{}) {
|
||||
Formatter{}.MergeAndFormatKVs(b, first, second)
|
||||
}
|
||||
|
||||
const missingValue = "(MISSING)"
|
||||
|
||||
// KVListFormat serializes all key/value pairs into the provided buffer.
|
||||
// A space gets inserted before the first pair and between each pair.
|
||||
func (f Formatter) KVListFormat(b *bytes.Buffer, keysAndValues ...interface{}) {
|
||||
for i := 0; i < len(keysAndValues); i += 2 {
|
||||
var v interface{}
|
||||
k := keysAndValues[i]
|
||||
if i+1 < len(keysAndValues) {
|
||||
v = keysAndValues[i+1]
|
||||
} else {
|
||||
v = missingValue
|
||||
func FormatKVs(b *bytes.Buffer, kvs ...[]interface{}) {
|
||||
Formatter{}.FormatKVs(b, kvs...)
|
||||
}
|
||||
|
||||
// FormatKVs formats all key/value pairs such that the output contains no
|
||||
// duplicates ("last one wins").
|
||||
func (f Formatter) FormatKVs(b *bytes.Buffer, kvs ...[]interface{}) {
|
||||
// De-duplication is done by optimistically formatting all key value
|
||||
// pairs and then cutting out the output of those key/value pairs which
|
||||
// got overwritten later.
|
||||
//
|
||||
// In the common case of no duplicates, the only overhead is tracking
|
||||
// previous keys. This uses a slice with a simple linear search because
|
||||
// the number of entries is typically so low that allocating a map or
|
||||
// keeping a sorted slice with binary search aren't justified.
|
||||
//
|
||||
// Using a fixed size here makes the Go compiler use the stack as
|
||||
// initial backing store for the slice, which is crucial for
|
||||
// performance.
|
||||
existing := make([]obsoleteKV, 0, 32)
|
||||
obsolete := make([]interval, 0, 32) // Sorted by start index.
|
||||
for _, keysAndValues := range kvs {
|
||||
for i := 0; i < len(keysAndValues); i += 2 {
|
||||
var v interface{}
|
||||
k := keysAndValues[i]
|
||||
if i+1 < len(keysAndValues) {
|
||||
v = keysAndValues[i+1]
|
||||
} else {
|
||||
v = missingValue
|
||||
}
|
||||
var e obsoleteKV
|
||||
e.start = b.Len()
|
||||
e.key = f.KVFormat(b, k, v)
|
||||
e.end = b.Len()
|
||||
i := findObsoleteEntry(existing, e.key)
|
||||
if i >= 0 {
|
||||
data := b.Bytes()
|
||||
if bytes.Compare(data[existing[i].start:existing[i].end], data[e.start:e.end]) == 0 {
|
||||
// The new entry gets obsoleted because it's identical.
|
||||
// This has the advantage that key/value pairs from
|
||||
// a WithValues call always come first, even if the same
|
||||
// pair gets added again later. This makes different log
|
||||
// entries more consistent.
|
||||
//
|
||||
// The new entry has a higher start index and thus can be appended.
|
||||
obsolete = append(obsolete, e.interval)
|
||||
} else {
|
||||
// The old entry gets obsoleted because it's value is different.
|
||||
//
|
||||
// Sort order is not guaranteed, we have to insert at the right place.
|
||||
index, _ := slices.BinarySearchFunc(obsolete, existing[i].interval, func(a, b interval) int { return a.start - b.start })
|
||||
obsolete = slices.Insert(obsolete, index, existing[i].interval)
|
||||
existing[i].interval = e.interval
|
||||
}
|
||||
} else {
|
||||
// Instead of appending at the end and doing a
|
||||
// linear search in findEntry, we could keep
|
||||
// the slice sorted by key and do a binary search.
|
||||
//
|
||||
// Above:
|
||||
// i, ok := slices.BinarySearchFunc(existing, e, func(a, b entry) int { return strings.Compare(a.key, b.key) })
|
||||
// Here:
|
||||
// existing = slices.Insert(existing, i, e)
|
||||
//
|
||||
// But that adds a dependency on the slices package
|
||||
// and made performance slightly worse, presumably
|
||||
// because the cost of shifting entries around
|
||||
// did not pay of with faster lookups.
|
||||
existing = append(existing, e)
|
||||
}
|
||||
}
|
||||
f.KVFormat(b, k, v)
|
||||
}
|
||||
|
||||
// If we need to remove some obsolete key/value pairs then move the memory.
|
||||
if len(obsolete) > 0 {
|
||||
// Potentially the next remaining output (might itself be obsolete).
|
||||
from := obsolete[0].end
|
||||
// Next obsolete entry.
|
||||
nextObsolete := 1
|
||||
// This is the source buffer, before truncation.
|
||||
all := b.Bytes()
|
||||
b.Truncate(obsolete[0].start)
|
||||
|
||||
for nextObsolete < len(obsolete) {
|
||||
if from == obsolete[nextObsolete].start {
|
||||
// Skip also the next obsolete key/value.
|
||||
from = obsolete[nextObsolete].end
|
||||
nextObsolete++
|
||||
continue
|
||||
}
|
||||
|
||||
// Preserve some output. Write uses copy, which
|
||||
// explicitly allows source and destination to overlap.
|
||||
// That could happen here.
|
||||
valid := all[from:obsolete[nextObsolete].start]
|
||||
b.Write(valid)
|
||||
from = obsolete[nextObsolete].end
|
||||
nextObsolete++
|
||||
}
|
||||
// Copy end of buffer.
|
||||
valid := all[from:]
|
||||
b.Write(valid)
|
||||
}
|
||||
}
|
||||
|
||||
func KVListFormat(b *bytes.Buffer, keysAndValues ...interface{}) {
|
||||
Formatter{}.KVListFormat(b, keysAndValues...)
|
||||
type obsoleteKV struct {
|
||||
key string
|
||||
interval
|
||||
}
|
||||
|
||||
func KVFormat(b *bytes.Buffer, k, v interface{}) {
|
||||
Formatter{}.KVFormat(b, k, v)
|
||||
// interval includes the start and excludes the end.
|
||||
type interval struct {
|
||||
start int
|
||||
end int
|
||||
}
|
||||
|
||||
func findObsoleteEntry(entries []obsoleteKV, key string) int {
|
||||
for i, entry := range entries {
|
||||
if entry.key == key {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// formatAny is the fallback formatter for a value. It supports a hook (for
|
||||
// example, for YAML encoding) and itself uses JSON encoding.
|
||||
func (f Formatter) formatAny(b *bytes.Buffer, v interface{}) {
|
||||
b.WriteRune('=')
|
||||
if f.AnyToStringHook != nil {
|
||||
b.WriteString(f.AnyToStringHook(v))
|
||||
str := f.AnyToStringHook(v)
|
||||
if strings.Contains(str, "\n") {
|
||||
// If it's multi-line, then pass it through writeStringValue to get start/end delimiters,
|
||||
// which separates it better from any following key/value pair.
|
||||
writeStringValue(b, str)
|
||||
return
|
||||
}
|
||||
// Otherwise put it directly after the separator, on the same lime,
|
||||
// The assumption is that the hook returns something where start/end are obvious.
|
||||
b.WriteRune('=')
|
||||
b.WriteString(str)
|
||||
return
|
||||
}
|
||||
b.WriteRune('=')
|
||||
formatAsJSON(b, v)
|
||||
}
|
||||
|
||||
|
||||
+7
-3
@@ -28,7 +28,7 @@ import (
|
||||
|
||||
// KVFormat serializes one key/value pair into the provided buffer.
|
||||
// A space gets inserted before the pair.
|
||||
func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) {
|
||||
func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) string {
|
||||
// This is the version without slog support. Must be kept in sync with
|
||||
// the version in keyvalues_slog.go.
|
||||
|
||||
@@ -37,13 +37,15 @@ func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) {
|
||||
// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments
|
||||
// for the sake of performance. Keys with spaces,
|
||||
// special characters, etc. will break parsing.
|
||||
var key string
|
||||
if sK, ok := k.(string); ok {
|
||||
// Avoid one allocation when the key is a string, which
|
||||
// normally it should be.
|
||||
b.WriteString(sK)
|
||||
key = sK
|
||||
} else {
|
||||
b.WriteString(fmt.Sprintf("%s", k))
|
||||
key = fmt.Sprintf("%s", k)
|
||||
}
|
||||
b.WriteString(key)
|
||||
|
||||
// The type checks are sorted so that more frequently used ones
|
||||
// come first because that is then faster in the common
|
||||
@@ -94,4 +96,6 @@ func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) {
|
||||
default:
|
||||
f.formatAny(b, v)
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
+8
-4
@@ -29,8 +29,8 @@ import (
|
||||
)
|
||||
|
||||
// KVFormat serializes one key/value pair into the provided buffer.
|
||||
// A space gets inserted before the pair.
|
||||
func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) {
|
||||
// A space gets inserted before the pair. It returns the key.
|
||||
func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) string {
|
||||
// This is the version without slog support. Must be kept in sync with
|
||||
// the version in keyvalues_slog.go.
|
||||
|
||||
@@ -39,13 +39,15 @@ func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) {
|
||||
// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments
|
||||
// for the sake of performance. Keys with spaces,
|
||||
// special characters, etc. will break parsing.
|
||||
var key string
|
||||
if sK, ok := k.(string); ok {
|
||||
// Avoid one allocation when the key is a string, which
|
||||
// normally it should be.
|
||||
b.WriteString(sK)
|
||||
key = sK
|
||||
} else {
|
||||
b.WriteString(fmt.Sprintf("%s", k))
|
||||
key = fmt.Sprintf("%s", k)
|
||||
}
|
||||
b.WriteString(key)
|
||||
|
||||
// The type checks are sorted so that more frequently used ones
|
||||
// come first because that is then faster in the common
|
||||
@@ -112,6 +114,8 @@ func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) {
|
||||
default:
|
||||
f.formatAny(b, v)
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
// generateJSON has the same preference for plain strings as KVFormat.
|
||||
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
Copyright 2013 Google Inc. All Rights Reserved.
|
||||
Copyright 2022 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package verbosity
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// New returns a struct that implements -v and -vmodule support. Changing and
|
||||
// checking these settings is thread-safe, with all concurrency issues handled
|
||||
// internally.
|
||||
func New() *VState {
|
||||
vs := new(VState)
|
||||
|
||||
// The two fields must have a pointer to the overal struct for their
|
||||
// implementation of Set.
|
||||
vs.vmodule.vs = vs
|
||||
vs.verbosity.vs = vs
|
||||
|
||||
return vs
|
||||
}
|
||||
|
||||
// Value is an extension that makes it possible to use the values in pflag.
|
||||
type Value interface {
|
||||
flag.Value
|
||||
Type() string
|
||||
}
|
||||
|
||||
func (vs *VState) V() Value {
|
||||
return &vs.verbosity
|
||||
}
|
||||
|
||||
func (vs *VState) VModule() Value {
|
||||
return &vs.vmodule
|
||||
}
|
||||
|
||||
// VState contains settings and state. Some of its fields can be accessed
|
||||
// through atomic read/writes, in other cases a mutex must be held.
|
||||
type VState struct {
|
||||
mu sync.Mutex
|
||||
|
||||
// These flags are modified only under lock, although verbosity may be fetched
|
||||
// safely using atomic.LoadInt32.
|
||||
vmodule moduleSpec // The state of the -vmodule flag.
|
||||
verbosity levelSpec // V logging level, the value of the -v flag/
|
||||
|
||||
// pcs is used in V to avoid an allocation when computing the caller's PC.
|
||||
pcs [1]uintptr
|
||||
// vmap is a cache of the V Level for each V() call site, identified by PC.
|
||||
// It is wiped whenever the vmodule flag changes state.
|
||||
vmap map[uintptr]Level
|
||||
// filterLength stores the length of the vmodule filter chain. If greater
|
||||
// than zero, it means vmodule is enabled. It may be read safely
|
||||
// using sync.LoadInt32, but is only modified under mu.
|
||||
filterLength int32
|
||||
}
|
||||
|
||||
// Level must be an int32 to support atomic read/writes.
|
||||
type Level int32
|
||||
|
||||
type levelSpec struct {
|
||||
vs *VState
|
||||
l Level
|
||||
}
|
||||
|
||||
// get returns the value of the level.
|
||||
func (l *levelSpec) get() Level {
|
||||
return Level(atomic.LoadInt32((*int32)(&l.l)))
|
||||
}
|
||||
|
||||
// set sets the value of the level.
|
||||
func (l *levelSpec) set(val Level) {
|
||||
atomic.StoreInt32((*int32)(&l.l), int32(val))
|
||||
}
|
||||
|
||||
// String is part of the flag.Value interface.
|
||||
func (l *levelSpec) String() string {
|
||||
return strconv.FormatInt(int64(l.l), 10)
|
||||
}
|
||||
|
||||
// Get is part of the flag.Getter interface. It returns the
|
||||
// verbosity level as int32.
|
||||
func (l *levelSpec) Get() interface{} {
|
||||
return int32(l.l)
|
||||
}
|
||||
|
||||
// Type is part of pflag.Value.
|
||||
func (l *levelSpec) Type() string {
|
||||
return "Level"
|
||||
}
|
||||
|
||||
// Set is part of the flag.Value interface.
|
||||
func (l *levelSpec) Set(value string) error {
|
||||
v, err := strconv.ParseInt(value, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.vs.mu.Lock()
|
||||
defer l.vs.mu.Unlock()
|
||||
l.vs.set(Level(v), l.vs.vmodule.filter, false)
|
||||
return nil
|
||||
}
|
||||
|
||||
// moduleSpec represents the setting of the -vmodule flag.
|
||||
type moduleSpec struct {
|
||||
vs *VState
|
||||
filter []modulePat
|
||||
}
|
||||
|
||||
// modulePat contains a filter for the -vmodule flag.
|
||||
// It holds a verbosity level and a file pattern to match.
|
||||
type modulePat struct {
|
||||
pattern string
|
||||
literal bool // The pattern is a literal string
|
||||
level Level
|
||||
}
|
||||
|
||||
// match reports whether the file matches the pattern. It uses a string
|
||||
// comparison if the pattern contains no metacharacters.
|
||||
func (m *modulePat) match(file string) bool {
|
||||
if m.literal {
|
||||
return file == m.pattern
|
||||
}
|
||||
match, _ := filepath.Match(m.pattern, file)
|
||||
return match
|
||||
}
|
||||
|
||||
func (m *moduleSpec) String() string {
|
||||
// Lock because the type is not atomic. TODO: clean this up.
|
||||
// Empty instances don't have and don't need a lock (can
|
||||
// happen when flag uses introspection).
|
||||
if m.vs != nil {
|
||||
m.vs.mu.Lock()
|
||||
defer m.vs.mu.Unlock()
|
||||
}
|
||||
var b bytes.Buffer
|
||||
for i, f := range m.filter {
|
||||
if i > 0 {
|
||||
b.WriteRune(',')
|
||||
}
|
||||
fmt.Fprintf(&b, "%s=%d", f.pattern, f.level)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the
|
||||
// struct is not exported.
|
||||
func (m *moduleSpec) Get() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type is part of pflag.Value
|
||||
func (m *moduleSpec) Type() string {
|
||||
return "pattern=N,..."
|
||||
}
|
||||
|
||||
var errVmoduleSyntax = errors.New("syntax error: expect comma-separated list of filename=N")
|
||||
|
||||
// Set will sets module value
|
||||
// Syntax: -vmodule=recordio=2,file=1,gfs*=3
|
||||
func (m *moduleSpec) Set(value string) error {
|
||||
var filter []modulePat
|
||||
for _, pat := range strings.Split(value, ",") {
|
||||
if len(pat) == 0 {
|
||||
// Empty strings such as from a trailing comma can be ignored.
|
||||
continue
|
||||
}
|
||||
patLev := strings.Split(pat, "=")
|
||||
if len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 {
|
||||
return errVmoduleSyntax
|
||||
}
|
||||
pattern := patLev[0]
|
||||
v, err := strconv.ParseInt(patLev[1], 10, 32)
|
||||
if err != nil {
|
||||
return errors.New("syntax error: expect comma-separated list of filename=N")
|
||||
}
|
||||
if v < 0 {
|
||||
return errors.New("negative value for vmodule level")
|
||||
}
|
||||
if v == 0 {
|
||||
continue // Ignore. It's harmless but no point in paying the overhead.
|
||||
}
|
||||
// TODO: check syntax of filter?
|
||||
filter = append(filter, modulePat{pattern, isLiteral(pattern), Level(v)})
|
||||
}
|
||||
m.vs.mu.Lock()
|
||||
defer m.vs.mu.Unlock()
|
||||
m.vs.set(m.vs.verbosity.l, filter, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// isLiteral reports whether the pattern is a literal string, that is, has no metacharacters
|
||||
// that require filepath.Match to be called to match the pattern.
|
||||
func isLiteral(pattern string) bool {
|
||||
return !strings.ContainsAny(pattern, `\*?[]`)
|
||||
}
|
||||
|
||||
// set sets a consistent state for V logging.
|
||||
// The mutex must be held.
|
||||
func (vs *VState) set(l Level, filter []modulePat, setFilter bool) {
|
||||
// Turn verbosity off so V will not fire while we are in transition.
|
||||
vs.verbosity.set(0)
|
||||
// Ditto for filter length.
|
||||
atomic.StoreInt32(&vs.filterLength, 0)
|
||||
|
||||
// Set the new filters and wipe the pc->Level map if the filter has changed.
|
||||
if setFilter {
|
||||
vs.vmodule.filter = filter
|
||||
vs.vmap = make(map[uintptr]Level)
|
||||
}
|
||||
|
||||
// Things are consistent now, so enable filtering and verbosity.
|
||||
// They are enabled in order opposite to that in V.
|
||||
atomic.StoreInt32(&vs.filterLength, int32(len(filter)))
|
||||
vs.verbosity.set(l)
|
||||
}
|
||||
|
||||
// Enabled checks whether logging is enabled at the given level. This must be
|
||||
// called with depth=0 when the caller of enabled will do the logging and
|
||||
// higher values when more stack levels need to be skipped.
|
||||
//
|
||||
// The mutex will be locked only if needed.
|
||||
func (vs *VState) Enabled(level Level, depth int) bool {
|
||||
// This function tries hard to be cheap unless there's work to do.
|
||||
// The fast path is two atomic loads and compares.
|
||||
|
||||
// Here is a cheap but safe test to see if V logging is enabled globally.
|
||||
if vs.verbosity.get() >= level {
|
||||
return true
|
||||
}
|
||||
|
||||
// It's off globally but vmodule may still be set.
|
||||
// Here is another cheap but safe test to see if vmodule is enabled.
|
||||
if atomic.LoadInt32(&vs.filterLength) > 0 {
|
||||
// Now we need a proper lock to use the logging structure. The pcs field
|
||||
// is shared so we must lock before accessing it. This is fairly expensive,
|
||||
// but if V logging is enabled we're slow anyway.
|
||||
vs.mu.Lock()
|
||||
defer vs.mu.Unlock()
|
||||
if runtime.Callers(depth+2, vs.pcs[:]) == 0 {
|
||||
return false
|
||||
}
|
||||
// runtime.Callers returns "return PCs", but we want
|
||||
// to look up the symbolic information for the call,
|
||||
// so subtract 1 from the PC. runtime.CallersFrames
|
||||
// would be cleaner, but allocates.
|
||||
pc := vs.pcs[0] - 1
|
||||
v, ok := vs.vmap[pc]
|
||||
if !ok {
|
||||
v = vs.setV(pc)
|
||||
}
|
||||
return v >= level
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// setV computes and remembers the V level for a given PC
|
||||
// when vmodule is enabled.
|
||||
// File pattern matching takes the basename of the file, stripped
|
||||
// of its .go suffix, and uses filepath.Match, which is a little more
|
||||
// general than the *? matching used in C++.
|
||||
// Mutex is held.
|
||||
func (vs *VState) setV(pc uintptr) Level {
|
||||
fn := runtime.FuncForPC(pc)
|
||||
file, _ := fn.FileLine(pc)
|
||||
// The file is something like /a/b/c/d.go. We want just the d.
|
||||
file = strings.TrimSuffix(file, ".go")
|
||||
if slash := strings.LastIndex(file, "/"); slash >= 0 {
|
||||
file = file[slash+1:]
|
||||
}
|
||||
for _, filter := range vs.vmodule.filter {
|
||||
if filter.match(file) {
|
||||
vs.vmap[pc] = filter.level
|
||||
return filter.level
|
||||
}
|
||||
}
|
||||
vs.vmap[pc] = 0
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user