github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/libs/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  		switch keyvals[i] {
    64  		case kitlevel.Key():
    65  			excludeIndexes = append(excludeIndexes, i)
    66  			switch keyvals[i+1].(type) { // nolint:gocritic
    67  			case string:
    68  				lvl = keyvals[i+1].(string)
    69  			case kitlevel.Value:
    70  				lvl = keyvals[i+1].(kitlevel.Value).String()
    71  			default:
    72  				panic(fmt.Sprintf("level value of unknown type %T", keyvals[i+1]))
    73  			}
    74  			// and message
    75  		case msgKey:
    76  			excludeIndexes = append(excludeIndexes, i)
    77  			msg = keyvals[i+1].(string)
    78  			// and module (could be multiple keyvals; if such case last keyvalue wins)
    79  		case moduleKey:
    80  			excludeIndexes = append(excludeIndexes, i)
    81  			module = keyvals[i+1].(string)
    82  		}
    83  	}
    84  
    85  	// Form a custom Tendermint line
    86  	//
    87  	// Example:
    88  	//     D[2016-05-02|11:06:44.322]   Stopping AddrBook (ignoring: already stopped)
    89  	//
    90  	// Description:
    91  	//     D										- first character of the level, uppercase (ASCII only)
    92  	//     [2016-05-02|11:06:44.322]    - our time format (see https://golang.org/src/time/format.go)
    93  	//     Stopping ...					- message
    94  	enc.buf.WriteString(fmt.Sprintf("%c[%s] %-44s ", lvl[0]-32, time.Now().Format("2006-01-02|15:04:05.000"), msg))
    95  
    96  	if module != unknown {
    97  		enc.buf.WriteString("module=" + module + " ")
    98  	}
    99  
   100  KeyvalueLoop:
   101  	for i := 0; i < len(keyvals)-1; i += 2 {
   102  		for _, j := range excludeIndexes {
   103  			if i == j {
   104  				continue KeyvalueLoop
   105  			}
   106  		}
   107  
   108  		err := enc.EncodeKeyval(keyvals[i], keyvals[i+1])
   109  		if err == logfmt.ErrUnsupportedValueType {
   110  			enc.EncodeKeyval(keyvals[i], fmt.Sprintf("%+v", keyvals[i+1])) //nolint:errcheck // no need to check error again
   111  		} else if err != nil {
   112  			return err
   113  		}
   114  	}
   115  
   116  	// Add newline to the end of the buffer
   117  	if err := enc.EndRecord(); err != nil {
   118  		return err
   119  	}
   120  
   121  	// The Logger interface requires implementations to be safe for concurrent
   122  	// use by multiple goroutines. For this implementation that means making
   123  	// only one call to l.w.Write() for each call to Log.
   124  	if _, err := l.w.Write(enc.buf.Bytes()); err != nil {
   125  		return err
   126  	}
   127  	return nil
   128  }