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

     1  // Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ (With no dependency injection, the code had to be modified to integrate with github.com/puellanivis/breton/lib/gnuflag)
     2  //
     3  // Copyright 2013 Google Inc. All Rights Reserved.
     4  //
     5  // Licensed under the Apache License, Version 2.0 (the "License");
     6  // you may not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing, software
    12  // distributed under the License is distributed on an "AS IS" BASIS,
    13  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  
    17  // Package glog implements logging analogous to the Google-internal C++ INFO/ERROR/V setup.
    18  // It provides functions Info, Warning, Error, Fatal, plus formatting variants such as
    19  // Infof. It also provides V-style logging controlled by the --verbosity and --vmodule=file=2 flags.
    20  //
    21  // Basic examples:
    22  //
    23  //	glog.Info("Prepare to repel boarders")
    24  //
    25  //	glog.Fatalf("Initialization failed: %s", err)
    26  //
    27  // See the documentation for the V function for an explanation of these examples:
    28  //
    29  //	if glog.V(2) {
    30  //		log.Info("Starting transaction...")
    31  //	}
    32  //
    33  //	glog.V(2).Infoln("Processed", nItems, "elements")
    34  //
    35  // Log output is buffered and written periodically using Flush. Programs
    36  // should call Flush before exiting to guarantee all log output is written.
    37  //
    38  // By default, all log statements write to files in a temporary directory.
    39  // This package provides several flags that modify this behavior.
    40  // As a result, flag.Parse must be called before any logging is done.
    41  //
    42  //	--logtostderr=false
    43  //		Logs are written to standard error instead of to files.
    44  //	--alsologtostderr=false
    45  //		Logs are written to standard error as well as to files.
    46  //	--stderrthreshold=ERROR
    47  //		Log events at or above this severity are logged to standard
    48  //		error as well as to files.
    49  //	--log_dir=""
    50  //		Log files will be written to this directory instead of the
    51  //		default temporary directory.
    52  //
    53  //	Other flags provide aids to debugging.
    54  //
    55  //	--log_backtrace_at=""
    56  //		When set to a file and line number holding a logging statement,
    57  //		such as
    58  //			--log_backtrace_at=gopherflakes.go:234
    59  //		a stack trace will be written to the Info log whenever execution
    60  //		hits that statement. (Unlike with --vmodule, the ".go" must be
    61  //		present.)
    62  //	--verbosity=0
    63  //		Enable V-leveled logging at the specified level.
    64  //	--vmodule=""
    65  //		The syntax of the argument is a comma-separated list of pattern=N,
    66  //		where pattern is a literal file name (minus the ".go" suffix) or
    67  //		"glob" pattern and N is a V level. For instance,
    68  //			--vmodule=gopher*=3
    69  //		sets the V level to 3 in all Go files whose names begin "gopher".
    70  package glog
    71  
    72  import (
    73  	"bufio"
    74  	"bytes"
    75  	"fmt"
    76  	"io"
    77  	stdLog "log"
    78  	"os"
    79  	"runtime"
    80  	"strconv"
    81  	"strings"
    82  	"sync"
    83  	"sync/atomic"
    84  	"time"
    85  
    86  	flag "github.com/puellanivis/breton/lib/gnuflag"
    87  )
    88  
    89  // flushSyncWriter is the interface satisfied by logging destinations.
    90  type flushSyncWriter interface {
    91  	Flush() error
    92  	Sync() error
    93  	io.Writer
    94  }
    95  
    96  const flushInterval = 30 * time.Second
    97  
    98  func init() {
    99  	go func() {
   100  		for range time.Tick(flushInterval) {
   101  			Flush()
   102  		}
   103  	}()
   104  }
   105  
   106  // loggingT collects all the global state of the logging setup.
   107  type loggingT struct {
   108  	// This is used to synchronize logging.
   109  	sync.Mutex
   110  
   111  	flagT
   112  
   113  	// file holds writer for each of the log types.
   114  	file [numSeverity]flushSyncWriter
   115  }
   116  
   117  var logging loggingT
   118  
   119  // Flush flushes all pending log I/O.
   120  func Flush() {
   121  	logging.Lock()
   122  	defer logging.Unlock()
   123  
   124  	logging.flushAll()
   125  }
   126  
   127  // flushAll flushes all the logs and attempts to "sync" their data to disk.
   128  // One is expected to be holding the loggingT.Mutex
   129  func (l *loggingT) flushAll() {
   130  	// Flush from fatal down, in case there's trouble flushing.
   131  	for s := fatalLog; s >= infoLog; s-- {
   132  		if file := l.file[s]; file != nil {
   133  			_ = file.Flush()
   134  			_ = file.Sync()
   135  		}
   136  	}
   137  }
   138  
   139  var timeNow = time.Now // Stubbed out for testing.
   140  
   141  /*
   142  header formats a log header as defined by the C++ implementation.
   143  It returns a buffer containing the formatted header and the user's file and line number.
   144  The depth specifies how many stack frames above lives the source line to be identified in the log message.
   145  
   146  Log lines have this form:
   147  
   148  	Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg...
   149  
   150  where the fields are defined as follows:
   151  
   152  	L                A single character, representing the log level (eg 'I' for INFO)
   153  	mm               The month (zero padded; ie May is '05')
   154  	dd               The day (zero padded)
   155  	hh:mm:ss.uuuuuu  Time in hours, minutes and fractional seconds
   156  	threadid         The space-padded thread ID as returned by GetTID()
   157  	file             The file name
   158  	line             The line number
   159  	msg              The user-supplied message
   160  */
   161  func (l *loggingT) header(s severity, depth int) (*bytes.Buffer, string, int) {
   162  	_, file, line, ok := runtime.Caller(3 + depth)
   163  	if !ok {
   164  		file = "???"
   165  		line = 1
   166  	}
   167  
   168  	if slash := strings.LastIndexByte(file, '/'); slash >= 0 {
   169  		file = file[slash+1:]
   170  	}
   171  
   172  	return l.formatHeader(s, file, line), file, line
   173  }
   174  
   175  // formatHeader formats a log header using the provided file name and line number.
   176  func (l *loggingT) formatHeader(s severity, file string, line int) *bytes.Buffer {
   177  	now := timeNow()
   178  
   179  	if line < 0 {
   180  		line = 0 // not a real line number, but acceptable to someDigits
   181  	}
   182  
   183  	if s > fatalLog {
   184  		s = infoLog // for safety.
   185  	}
   186  	buf := getBuffer()
   187  
   188  	// Thinks are simple enough, Fprintf uses a lot of reflection that is unnecessary,
   189  	// but bytes.Buffer already uses a small bootstrap array,
   190  	// and strconv.Itoa has some amazingly clever speed up to handle small values.
   191  	_, month, day := now.Date()
   192  	hour, minute, second := now.Clock()
   193  	ns := now.Nanosecond() / 1000
   194  
   195  	// Lmmdd hh:mm:ss.uuuuuu threadid file:line]
   196  	buf.WriteByte(severityChars[s])
   197  	writeTwo(buf, int(month))
   198  	writeTwo(buf, day)
   199  	buf.WriteByte(' ')
   200  
   201  	writeTwo(buf, hour)
   202  	buf.WriteByte(':')
   203  	writeTwo(buf, minute)
   204  	buf.WriteByte(':')
   205  	writeTwo(buf, second)
   206  	buf.WriteByte('.')
   207  
   208  	nano := strconv.Itoa(ns)
   209  	if len(nano) < 6 {
   210  		buf.WriteString("000000"[len(nano):])
   211  	}
   212  	buf.WriteString(nano)
   213  	buf.WriteByte(' ')
   214  
   215  	buf.Write(tid)
   216  	buf.WriteByte(' ')
   217  
   218  	buf.WriteString(file)
   219  	buf.WriteByte(':')
   220  
   221  	buf.WriteString(strconv.Itoa(line))
   222  	buf.WriteByte(']')
   223  	buf.WriteByte(' ')
   224  
   225  	return buf
   226  }
   227  
   228  func (l *loggingT) println(s severity, args ...interface{}) {
   229  	buf, file, line := l.header(s, 0)
   230  	fmt.Fprintln(buf, args...)
   231  	l.output(s, buf, file, line, false)
   232  }
   233  
   234  func (l *loggingT) printDepth(s severity, depth int, args ...interface{}) {
   235  	buf, file, line := l.header(s, depth)
   236  
   237  	fmt.Fprint(buf, args...)
   238  	ensureNL(buf)
   239  
   240  	l.output(s, buf, file, line, false)
   241  }
   242  
   243  func (l *loggingT) print(s severity, args ...interface{}) {
   244  	l.printDepth(s, 1, args...)
   245  }
   246  
   247  func (l *loggingT) printf(s severity, format string, args ...interface{}) {
   248  	buf, file, line := l.header(s, 0)
   249  
   250  	fmt.Fprintf(buf, format, args...)
   251  	ensureNL(buf)
   252  
   253  	l.output(s, buf, file, line, false)
   254  }
   255  
   256  // output writes the data to the log files and releases the buffer.
   257  func (l *loggingT) output(s severity, buf *bytes.Buffer, file string, line int, alsoToStderr bool) {
   258  	if l.TraceLocation.isSet() {
   259  		if l.TraceLocation.match(file, line) {
   260  			buf.Write(stacks(false))
   261  		}
   262  	}
   263  	data := buf.Bytes()
   264  
   265  	l.Lock()
   266  
   267  	if !flag.Parsed() {
   268  		os.Stderr.Write([]byte("ERROR: logging before flag.Parse: "))
   269  		os.Stderr.Write(data)
   270  
   271  	} else if l.ToStderr {
   272  		os.Stderr.Write(data)
   273  
   274  	} else {
   275  		if alsoToStderr || l.AlsoToStderr || s >= l.StderrThreshold.get() {
   276  			os.Stderr.Write(data)
   277  		}
   278  
   279  		if l.file[s] == nil {
   280  			if err := l.createFiles(s); err != nil {
   281  				os.Stderr.Write(data) // Make sure the message appears somewhere.
   282  				l.exit(err)
   283  			}
   284  		}
   285  
   286  		switch s {
   287  		case fatalLog:
   288  			_, _ = l.file[fatalLog].Write(data)
   289  			fallthrough
   290  		case errorLog:
   291  			_, _ = l.file[errorLog].Write(data)
   292  			fallthrough
   293  		case warningLog:
   294  			_, _ = l.file[warningLog].Write(data)
   295  			fallthrough
   296  		case infoLog:
   297  			_, _ = l.file[infoLog].Write(data)
   298  		}
   299  	}
   300  
   301  	if s == fatalLog {
   302  		// If we got here via Exit rather than Fatal, print no stacks.
   303  		if atomic.LoadUint32(&fatalNoStacks) > 0 {
   304  			l.Unlock()
   305  
   306  			timeoutFlush(10 * time.Second)
   307  			os.Exit(1)
   308  		}
   309  
   310  		// Dump all goroutine stacks before exiting.
   311  		// First, make sure we see the trace for the current goroutine on standard error.
   312  		// If --logtostderr has been specified, the loop below will do that anyway
   313  		// as the first stack in the full dump.
   314  		if !l.ToStderr {
   315  			os.Stderr.Write(stacks(false))
   316  		}
   317  
   318  		// Write the stack trace for all goroutines to the files.
   319  		trace := stacks(true)
   320  
   321  		for log := fatalLog; log >= infoLog; log-- {
   322  			if f := l.file[log]; f != nil { // Can be nil if -logtostderr is set.
   323  				_, _ = f.Write(trace)
   324  			}
   325  		}
   326  
   327  		l.Unlock()
   328  
   329  		timeoutFlush(10 * time.Second)
   330  		os.Exit(255) // C++ uses -1, which is silly because it's anded with 255 anyway.
   331  	}
   332  
   333  	l.Unlock()
   334  
   335  	putBuffer(buf)
   336  	if stats := severityStats[s]; stats != nil {
   337  		stats.add(int64(len(data)))
   338  	}
   339  }
   340  
   341  // timeoutFlush calls Flush and returns when it completes or after timeout
   342  // elapses, whichever happens first.  This is needed because the hooks invoked
   343  // by Flush may deadlock when glog.Fatal is called from a hook that holds
   344  // a lock.
   345  func timeoutFlush(timeout time.Duration) {
   346  	done := make(chan struct{})
   347  	go func() {
   348  		defer close(done)
   349  
   350  		Flush() // calls logging.lockAndFlushAll()
   351  	}()
   352  
   353  	select {
   354  	case <-done:
   355  	case <-time.After(timeout):
   356  		fmt.Fprintln(os.Stderr, "glog: Flush took longer than", timeout)
   357  	}
   358  }
   359  
   360  // stacks is a wrapper for runtime.Stack that attempts to recover the data for all goroutines.
   361  func stacks(all bool) []byte {
   362  	// We don't know how big the traces are, so grow a few times if they don't fit. Start large, though.
   363  	n := 10000
   364  	if all {
   365  		n = 100000
   366  	}
   367  	var trace []byte
   368  	for i := 0; i < 5; i++ {
   369  		trace = make([]byte, n)
   370  		nbytes := runtime.Stack(trace, all)
   371  		if nbytes < len(trace) {
   372  			return trace[:nbytes]
   373  		}
   374  		n *= 2
   375  	}
   376  	return trace
   377  }
   378  
   379  // exit is called if there is trouble creating or writing log files.
   380  // It flushes the logs and exits the program; there's no point in hanging around.
   381  // logging Mutex is held.
   382  func (l *loggingT) exit(err error) {
   383  	fmt.Fprintln(os.Stderr, "log: exiting because of error: ", err)
   384  	Flush()
   385  	os.Exit(2)
   386  }
   387  
   388  // syncBuffer joins a bufio.Writer to its underlying file, providing access to the
   389  // file's Sync method and providing a wrapper for the Write method that provides log
   390  // file rotation. There are conflicting methods, so the file cannot be embedded.
   391  // logging Mutex is held for all its methods.
   392  type syncBuffer struct {
   393  	logger *loggingT
   394  	sev    severity
   395  
   396  	*bufio.Writer
   397  	file   *os.File
   398  	nbytes uint64 // The number of bytes written to this file
   399  }
   400  
   401  func (sb *syncBuffer) Sync() error {
   402  	return sb.file.Sync()
   403  }
   404  
   405  func (sb *syncBuffer) Write(b []byte) (n int, err error) {
   406  	if sb.nbytes+uint64(len(b)) >= MaxSize {
   407  		if err := sb.rotateFile(time.Now()); err != nil {
   408  			sb.logger.exit(err)
   409  		}
   410  	}
   411  
   412  	n, err = sb.Writer.Write(b)
   413  	sb.nbytes += uint64(n)
   414  	if err != nil {
   415  		sb.logger.exit(err)
   416  	}
   417  
   418  	return n, err
   419  }
   420  
   421  // bufferSize sizes the buffer associated with each log file. It's large
   422  // so that log records can accumulate without the logging thread blocking
   423  // on disk I/O. The flushDaemon will block instead.
   424  const bufferSize = 256 * 1024
   425  
   426  // rotateFile closes the syncBuffer's file and starts a new one.
   427  func (sb *syncBuffer) rotateFile(now time.Time) error {
   428  	if sb.file != nil {
   429  		if err := sb.Flush(); err != nil {
   430  			return err
   431  		}
   432  
   433  		if err := sb.file.Close(); err != nil {
   434  			return err
   435  		}
   436  	}
   437  
   438  	var err error
   439  	sb.file, _, err = create(severityNames[sb.sev], now)
   440  	sb.nbytes = 0
   441  	if err != nil {
   442  		return err
   443  	}
   444  
   445  	sb.Writer = bufio.NewWriterSize(sb.file, bufferSize)
   446  
   447  	// Write header.
   448  	buf := new(bytes.Buffer)
   449  
   450  	fmt.Fprintf(buf, "Log file created at: %s\n", now.Format("2006/01/02 15:04:05"))
   451  	fmt.Fprintf(buf, "Running on machine: %s\n", host)
   452  	fmt.Fprintf(buf, "Binary: Built with %s %s for %s/%s\n", runtime.Compiler, runtime.Version(), runtime.GOOS, runtime.GOARCH)
   453  	fmt.Fprintf(buf, "Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg\n")
   454  
   455  	n, err := buf.WriteTo(sb.file)
   456  	sb.nbytes += uint64(n)
   457  	return err
   458  }
   459  
   460  // createFiles creates all the log files for severity from sev down to infoLog.
   461  // logging mutex is held.
   462  func (l *loggingT) createFiles(sev severity) error {
   463  	now := time.Now()
   464  
   465  	// Files are created in decreasing severity order, so as soon as we find one
   466  	// has already been created, we can stop.
   467  	for s := sev; s >= infoLog && l.file[s] == nil; s-- {
   468  		sb := &syncBuffer{
   469  			logger: l,
   470  			sev:    s,
   471  		}
   472  
   473  		if err := sb.rotateFile(now); err != nil {
   474  			return err
   475  		}
   476  
   477  		l.file[s] = sb
   478  	}
   479  
   480  	return nil
   481  }
   482  
   483  // CopyStandardLogTo arranges for messages written to the Go "log" package's
   484  // default logs to also appear in the Google logs for the named and lower
   485  // severities.  Subsequent changes to the standard log's default output location
   486  // or format may break this behavior.
   487  //
   488  // Valid names are "INFO", "WARNING", "ERROR", and "FATAL".  If the name is not
   489  // recognized, CopyStandardLogTo panics.
   490  func CopyStandardLogTo(name string) {
   491  	sev, ok := severityByName[name]
   492  	if !ok {
   493  		panic(fmt.Sprintf("log.CopyStandardLogTo(%q): unrecognized severity name", name))
   494  	}
   495  
   496  	// Set a log format that captures the user's file and line:
   497  	//   d.go:23: message
   498  	stdLog.SetFlags(stdLog.Lshortfile)
   499  	stdLog.SetOutput(logBridge(sev))
   500  }
   501  
   502  // logBridge provides the Write method that enables CopyStandardLogTo to connect
   503  // Go's standard logs to the logs provided by this package.
   504  type logBridge severity
   505  
   506  // Write parses the standard logging line and passes its components to the
   507  // logger for severity(lb).
   508  func (lb logBridge) Write(b []byte) (n int, err error) {
   509  	file, line := "???", 1
   510  	var text string
   511  
   512  	// Split "d.go:23: message" into "d.go", "23", and "message".
   513  	if parts := bytes.SplitN(b, []byte{':'}, 3); len(parts) != 3 || len(parts[0]) < 1 || len(parts[2]) < 1 {
   514  		text = fmt.Sprintf("bad log format: %s", b)
   515  
   516  	} else {
   517  		file = string(parts[0])
   518  		text = string(parts[2][1:]) // skip leading space
   519  
   520  		line, err = strconv.Atoi(string(parts[1]))
   521  		if err != nil {
   522  			text = fmt.Sprintf("bad line number: %s", b)
   523  			line = 1
   524  		}
   525  	}
   526  
   527  	buf := logging.formatHeader(severity(lb), file, line)
   528  
   529  	buf.WriteString(text)
   530  	ensureNL(buf)
   531  
   532  	// l.output with alsoToStderr=true, so standard log messages
   533  	// always appear on standard error.
   534  	logging.output(severity(lb), buf, file, line, true)
   535  
   536  	return len(b), nil
   537  }