github.com/puellanivis/breton@v0.2.16/lib/glog/flags.go (about)

     1  package glog
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"path/filepath"
     8  	"runtime"
     9  	"strconv"
    10  	"strings"
    11  	"sync"
    12  	"sync/atomic"
    13  
    14  	flag "github.com/puellanivis/breton/lib/gnuflag"
    15  )
    16  
    17  // severity identifies the sort of log: info, warning, etc.
    18  // It also implements the flag.Value interface.
    19  // The --stderrthreshold flag is of type severity and should be modified only through the flag.Value interface.
    20  // The values match the corresponding constants in C++.
    21  type severity int32 // sync/atomic int32
    22  
    23  // These constants identify the log levels in order of increasing severity.
    24  // A message written to a high-severity log file is also written to each
    25  // lower-severity log file.
    26  const (
    27  	infoLog severity = iota
    28  	warningLog
    29  	errorLog
    30  	fatalLog
    31  	numSeverity = 4
    32  )
    33  
    34  const severityChars = "IWEF"
    35  
    36  var severityNames = []string{
    37  	infoLog:    "INFO",
    38  	warningLog: "WARNING",
    39  	errorLog:   "ERROR",
    40  	fatalLog:   "FATAL",
    41  }
    42  
    43  var severityByName = map[string]severity{
    44  	"INFO":    infoLog,
    45  	"WARNING": warningLog,
    46  	"ERROR":   errorLog,
    47  	"FATAL":   fatalLog,
    48  }
    49  
    50  // get returns the value of the severity.
    51  func (s *severity) get() severity {
    52  	return severity(atomic.LoadInt32((*int32)(s)))
    53  }
    54  
    55  // set sets the value of the severity.
    56  func (s *severity) set(val severity) {
    57  	atomic.StoreInt32((*int32)(s), int32(val))
    58  }
    59  
    60  // String is part of the flag.Value interface.
    61  func (s *severity) String() string {
    62  	return strconv.FormatInt(int64(s.get()), 10)
    63  }
    64  
    65  // Get is part of the flag.Value interface.
    66  func (s *severity) Get() interface{} {
    67  	return s.get()
    68  }
    69  
    70  // Set is part of the flag.Value interface.
    71  func (s *severity) Set(value string) error {
    72  	var threshold severity
    73  
    74  	key := strings.ToUpper(value)
    75  	threshold, ok := severityByName[key]
    76  
    77  	// If it is not a known name, then parse it as an int.
    78  	if !ok {
    79  		v, err := strconv.Atoi(value)
    80  		if err != nil {
    81  			return err
    82  		}
    83  
    84  		threshold = severity(v)
    85  	}
    86  
    87  	s.set(threshold)
    88  	return nil
    89  }
    90  
    91  // Level is exported because it appears in the arguments to V and is
    92  // the type of the --verbosity flag, which can be set programmatically.
    93  // It's a distinct type because we want to discriminate it from logType.
    94  // Variables of type level are only changed under logging.mu.
    95  // The --verbosity flag is read only with atomic ops, so the state of the logging
    96  // module is consistent.
    97  
    98  // Level is treated as a sync/atomic int32.
    99  
   100  // Level specifies a level of verbosity for V logs. *Level implements
   101  // flag.Value; the --verbosity flag is of type Level and should be modified
   102  // only through the flag.Value interface.
   103  type Level int32 // sync/atomic int32
   104  
   105  // get returns the value of the Level.
   106  func (l *Level) get() Level {
   107  	return Level(atomic.LoadInt32((*int32)(l)))
   108  }
   109  
   110  // set sets the value of the Level.
   111  func (l *Level) set(val Level) {
   112  	atomic.StoreInt32((*int32)(l), int32(val))
   113  }
   114  
   115  // String is part of the flag.Value interface.
   116  func (l *Level) String() string {
   117  	return strconv.FormatInt(int64(l.get()), 10)
   118  }
   119  
   120  // Get is part of the flag.Value interface.
   121  func (l *Level) Get() interface{} {
   122  	return l.get()
   123  }
   124  
   125  // Set is part of the flag.Value interface.
   126  func (l *Level) Set(value string) error {
   127  	v, err := strconv.Atoi(value)
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	l.set(Level(v))
   133  	return nil
   134  }
   135  
   136  // modulePat contains a filter for the --vmodule flag.
   137  // It holds a verbosity level and a file pattern to match.
   138  type modulePat struct {
   139  	pattern string
   140  	literal bool // The pattern is a literal string
   141  	level   Level
   142  }
   143  
   144  // match reports whether the file matches the pattern. It uses a string
   145  // comparison if the pattern contains no metacharacters.
   146  func (m *modulePat) match(file string) bool {
   147  	if m.literal {
   148  		return file == m.pattern
   149  	}
   150  
   151  	match, _ := filepath.Match(m.pattern, file)
   152  	return match
   153  }
   154  
   155  // moduleSpec represents the setting of the --vmodule flag.
   156  type moduleSpec struct {
   157  	sync.RWMutex
   158  
   159  	set     int32
   160  	filters []modulePat
   161  	vmap    map[uintptr]Level
   162  }
   163  
   164  func (m *moduleSpec) isSet() bool {
   165  	return atomic.LoadInt32(&m.set) > 0
   166  }
   167  
   168  func (m *moduleSpec) getV(pc uintptr) Level {
   169  	m.RLock()
   170  	v, ok := m.vmap[pc]
   171  	m.RUnlock()
   172  
   173  	if ok {
   174  		return v
   175  	}
   176  
   177  	return m.setV(pc)
   178  }
   179  
   180  // setV computes and memoizes the V level for a given PC when vmodule is enabled.
   181  // File pattern matching takes the basename of the file, stripped of its .go suffix,
   182  // and uses filepath.Match, which is a little more general than the *? matching in C++.
   183  func (m *moduleSpec) setV(pc uintptr) Level {
   184  	frames := runtime.CallersFrames([]uintptr{pc})
   185  	frame, _ := frames.Next()
   186  
   187  	// The file is something like /a/b/c/d.go.
   188  	// We just want the d.
   189  	file := strings.TrimSuffix(frame.File, ".go")
   190  
   191  	if slash := strings.LastIndexByte(file, '/'); slash >= 0 {
   192  		file = file[slash+1:]
   193  	}
   194  
   195  	m.Lock()
   196  	defer m.Unlock()
   197  
   198  	if v, ok := m.vmap[pc]; ok {
   199  		// someone did the work for us while we were waiting on the lock.
   200  		return v
   201  	}
   202  
   203  	for _, filter := range m.filters {
   204  		if filter.match(file) {
   205  			m.vmap[pc] = filter.level
   206  			return filter.level
   207  		}
   208  	}
   209  
   210  	m.vmap[pc] = 0
   211  	return 0
   212  }
   213  
   214  func (m *moduleSpec) String() string {
   215  	// Lock because the type is not atomic. TODO: clean this up.
   216  	m.RLock()
   217  	defer m.RUnlock()
   218  
   219  	b := new(bytes.Buffer)
   220  	for i, f := range m.filters {
   221  		if i > 0 {
   222  			b.WriteByte(',')
   223  		}
   224  		b.WriteString(f.pattern)
   225  		b.WriteByte('=')
   226  		b.WriteString(strconv.Itoa(int(f.level)))
   227  	}
   228  
   229  	return b.String()
   230  }
   231  
   232  // Get is part of the (Go 1.2) flag.Getter interface.
   233  // It always returns nil for this flag type since the struct is not exported.
   234  func (m *moduleSpec) Get() interface{} {
   235  	return nil
   236  }
   237  
   238  var errVmoduleSyntax = errors.New("syntax error: expect comma-separated list of filename=N")
   239  
   240  // Syntax: --vmodule=recordio=2,file=1,gfs*=3
   241  func (m *moduleSpec) Set(value string) error {
   242  	var filters []modulePat
   243  
   244  	for _, pat := range strings.Split(value, ",") {
   245  		if len(pat) == 0 {
   246  			// Empty strings such as from a trailing comma can be ignored.
   247  			continue
   248  		}
   249  
   250  		patLev := strings.Split(pat, "=")
   251  		if len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 {
   252  			return errVmoduleSyntax
   253  		}
   254  
   255  		pattern := patLev[0]
   256  
   257  		v, err := strconv.Atoi(patLev[1])
   258  		if err != nil {
   259  			return errors.New("syntax error: expect comma-separated list of filename=N")
   260  		}
   261  
   262  		if v < 0 {
   263  			return errors.New("negative value for vmodule level")
   264  		}
   265  		if v == 0 {
   266  			continue // Ignore. It's harmless but no point in paying the overhead.
   267  		}
   268  
   269  		literal, err := isLiteral(pattern)
   270  		if err != nil {
   271  			return err
   272  		}
   273  
   274  		// TODO: check syntax of filter?
   275  		filters = append(filters, modulePat{
   276  			pattern: pattern,
   277  			literal: literal,
   278  			level:   Level(v),
   279  		})
   280  	}
   281  
   282  	m.filters = filters
   283  	m.vmap = make(map[uintptr]Level)
   284  	atomic.StoreInt32(&m.set, int32(len(filters)))
   285  
   286  	return nil
   287  }
   288  
   289  // isLiteral reports whether the pattern is a literal string,
   290  // that is, has no metacharacters that require filepath.Match to be called to match the pattern.
   291  //
   292  // The error returned should be filepath.ErrBadPattern,
   293  // but filepath should provide the check pattern for syntax, which it doesn’t.
   294  func isLiteral(pattern string) (bool, error) {
   295  	return !strings.ContainsAny(pattern, `\*?[]`), nil
   296  }
   297  
   298  // traceLocation represents the setting of the --log_backtrace_at flag.
   299  type traceLocation struct {
   300  	sync.RWMutex
   301  
   302  	file string
   303  	line int32
   304  }
   305  
   306  // isSet reports whether the trace location has been specified.
   307  // logging.mu is held.
   308  func (t *traceLocation) isSet() bool {
   309  	return atomic.LoadInt32(&t.line) > 0
   310  }
   311  
   312  // match reports whether the specified file and line matches the trace location.
   313  // The argument file name is the full path, not the basename specified in the flag.
   314  // logging.mu is held.
   315  func (t *traceLocation) match(file string, line int) bool {
   316  	// Lock because the type is not atomic. TODO: clean this up.
   317  	t.RLock()
   318  	defer t.RUnlock()
   319  
   320  	if atomic.LoadInt32(&t.line) != int32(line) {
   321  		return false
   322  	}
   323  
   324  	if i := strings.LastIndex(file, "/"); i >= 0 {
   325  		file = file[i+1:]
   326  	}
   327  
   328  	return t.file == file
   329  }
   330  
   331  func (t *traceLocation) String() string {
   332  	// Lock because the type is not atomic. TODO: clean this up.
   333  	t.RLock()
   334  	defer t.RUnlock()
   335  
   336  	return fmt.Sprintf("%s:%d", t.file, atomic.LoadInt32(&t.line))
   337  }
   338  
   339  // Get is part of the (Go 1.2) flag.Getter interface.
   340  // It always returns nil for this flag type since the struct is not exported.
   341  func (t *traceLocation) Get() interface{} {
   342  	return nil
   343  }
   344  
   345  var errTraceSyntax = errors.New("syntax error: expect file.go:234")
   346  
   347  // Syntax: --log_backtrace_at=gopherflakes.go:234
   348  // Note that unlike vmodule the file extension is included here.
   349  func (t *traceLocation) Set(value string) error {
   350  	t.Lock()
   351  	defer t.Unlock()
   352  
   353  	if value == "" {
   354  		// Unset.
   355  		t.line = 0
   356  		t.file = ""
   357  
   358  		return nil
   359  	}
   360  
   361  	fields := strings.Split(value, ":")
   362  	if len(fields) != 2 {
   363  		return errTraceSyntax
   364  	}
   365  
   366  	file, line := fields[0], fields[1]
   367  	if !strings.Contains(file, ".") {
   368  		return errTraceSyntax
   369  	}
   370  
   371  	v, err := strconv.Atoi(line)
   372  	if err != nil {
   373  		return errTraceSyntax
   374  	}
   375  
   376  	if v <= 0 {
   377  		return errors.New("negative or zero value for level")
   378  	}
   379  
   380  	t.file = file
   381  	atomic.StoreInt32(&t.line, int32(v))
   382  
   383  	return nil
   384  }
   385  
   386  var _ = flag.Struct("", &logging.flagT)
   387  
   388  // flagT collects all the flags of the logging setup.
   389  type flagT struct {
   390  	// Boolean flags. NOT ATOMIC or thread-safe.
   391  	ToStderr     bool `flag:"logtostderr"     desc:"log to standard error instead of files"`
   392  	AlsoToStderr bool `flag:"alsologtostderr" desc:"log to standard error as well as files"`
   393  
   394  	// Level flag. Handled atomically.
   395  	StderrThreshold severity `flag:"stderrthreshold,def=ERROR" desc:"logs at or above this ·threshold· go to stderr"`
   396  
   397  	// traceLocation is the state of the --log_backtrace_at flag.
   398  	TraceLocation traceLocation `flag:"log_backtrace_at" desc:"when logging hits line ·file:N·, emit a stack trace"`
   399  	// These flags are modified only under lock,
   400  	// although verbosity may be fetched safely using atomic.LoadInt32.
   401  	Vmodule   moduleSpec `flag:"vmodules"  desc:"comma-separated list of ·pattern=N· settings for file-filtered logging"`
   402  	Verbosity Level      `flag:"verbosity" desc:"log ·level· for V logs"`
   403  }