github.com/tendermint/tmlibs@v0.9.0/log/tmfmt_logger.go (about)

     1  package log
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"sync"
     8  	"time"
     9  
    10  	kitlog "github.com/go-kit/kit/log"
    11  	kitlevel "github.com/go-kit/kit/log/level"
    12  	"github.com/go-logfmt/logfmt"
    13  )
    14  
    15  type tmfmtEncoder struct {
    16  	*logfmt.Encoder
    17  	buf bytes.Buffer
    18  }
    19  
    20  func (l *tmfmtEncoder) Reset() {
    21  	l.Encoder.Reset()
    22  	l.buf.Reset()
    23  }
    24  
    25  var tmfmtEncoderPool = sync.Pool{
    26  	New: func() interface{} {
    27  		var enc tmfmtEncoder
    28  		enc.Encoder = logfmt.NewEncoder(&enc.buf)
    29  		return &enc
    30  	},
    31  }
    32  
    33  type tmfmtLogger struct {
    34  	w io.Writer
    35  }
    36  
    37  // NewTMFmtLogger returns a logger that encodes keyvals to the Writer in
    38  // Tendermint custom format. Note complex types (structs, maps, slices)
    39  // formatted as "%+v".
    40  //
    41  // Each log event produces no more than one call to w.Write.
    42  // The passed Writer must be safe for concurrent use by multiple goroutines if
    43  // the returned Logger will be used concurrently.
    44  func NewTMFmtLogger(w io.Writer) kitlog.Logger {
    45  	return &tmfmtLogger{w}
    46  }
    47  
    48  func (l tmfmtLogger) Log(keyvals ...interface{}) error {
    49  	enc := tmfmtEncoderPool.Get().(*tmfmtEncoder)
    50  	enc.Reset()
    51  	defer tmfmtEncoderPool.Put(enc)
    52  
    53  	const unknown = "unknown"
    54  	lvl := "none"
    55  	msg := unknown
    56  	module := unknown
    57  
    58  	// indexes of keys to skip while encoding later
    59  	excludeIndexes := make([]int, 0)
    60  
    61  	for i := 0; i < len(keyvals)-1; i += 2 {
    62  		// Extract level
    63  		if keyvals[i] == kitlevel.Key() {
    64  			excludeIndexes = append(excludeIndexes, i)
    65  			switch keyvals[i+1].(type) {
    66  			case string:
    67  				lvl = keyvals[i+1].(string)
    68  			case kitlevel.Value:
    69  				lvl = keyvals[i+1].(kitlevel.Value).String()
    70  			default:
    71  				panic(fmt.Sprintf("level value of unknown type %T", keyvals[i+1]))
    72  			}
    73  			// and message
    74  		} else if keyvals[i] == msgKey {
    75  			excludeIndexes = append(excludeIndexes, i)
    76  			msg = keyvals[i+1].(string)
    77  			// and module (could be multiple keyvals; if such case last keyvalue wins)
    78  		} else if keyvals[i] == moduleKey {
    79  			excludeIndexes = append(excludeIndexes, i)
    80  			module = keyvals[i+1].(string)
    81  		}
    82  	}
    83  
    84  	// Form a custom Tendermint line
    85  	//
    86  	// Example:
    87  	//     D[05-02|11:06:44.322] Stopping AddrBook (ignoring: already stopped)
    88  	//
    89  	// Description:
    90  	//     D										- first character of the level, uppercase (ASCII only)
    91  	//     [05-02|11:06:44.322] - our time format (see https://golang.org/src/time/format.go)
    92  	//     Stopping ...					- message
    93  	enc.buf.WriteString(fmt.Sprintf("%c[%s] %-44s ", lvl[0]-32, time.Now().UTC().Format("01-02|15:04:05.000"), msg))
    94  
    95  	if module != unknown {
    96  		enc.buf.WriteString("module=" + module + " ")
    97  	}
    98  
    99  KeyvalueLoop:
   100  	for i := 0; i < len(keyvals)-1; i += 2 {
   101  		for _, j := range excludeIndexes {
   102  			if i == j {
   103  				continue KeyvalueLoop
   104  			}
   105  		}
   106  
   107  		err := enc.EncodeKeyval(keyvals[i], keyvals[i+1])
   108  		if err == logfmt.ErrUnsupportedValueType {
   109  			enc.EncodeKeyval(keyvals[i], fmt.Sprintf("%+v", keyvals[i+1]))
   110  		} else if err != nil {
   111  			return err
   112  		}
   113  	}
   114  
   115  	// Add newline to the end of the buffer
   116  	if err := enc.EndRecord(); err != nil {
   117  		return err
   118  	}
   119  
   120  	// The Logger interface requires implementations to be safe for concurrent
   121  	// use by multiple goroutines. For this implementation that means making
   122  	// only one call to l.w.Write() for each call to Log.
   123  	if _, err := l.w.Write(enc.buf.Bytes()); err != nil {
   124  		return err
   125  	}
   126  	return nil
   127  }