github.com/emc-advanced-dev/unik@v0.0.0-20190717152701-a58d3e8e33b7/pkg/util/log_formatter.go (about)

     1  package util
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"runtime"
     7  	"sort"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/sirupsen/logrus"
    12  )
    13  
    14  //Redaction formatter
    15  
    16  const (
    17  	nocolor = 0
    18  	red     = 31
    19  	green   = 32
    20  	yellow  = 33
    21  	blue    = 34
    22  	gray    = 37
    23  )
    24  
    25  var (
    26  	baseTimestamp time.Time
    27  	isTerminal    bool
    28  )
    29  
    30  func init() {
    31  	baseTimestamp = time.Now()
    32  }
    33  
    34  func miniTS() int {
    35  	return int(time.Since(baseTimestamp) / time.Second)
    36  }
    37  
    38  type RedactedTextFormatter struct {
    39  	// Set to true to bypass checking for a TTY before outputting colors.
    40  	ForceColors bool
    41  
    42  	// Force disabling colors.
    43  	DisableColors bool
    44  
    45  	// Disable timestamp logging. useful when output is redirected to logging
    46  	// system that already adds timestamps.
    47  	DisableTimestamp bool
    48  
    49  	// Enable logging the full timestamp when a TTY is attached instead of just
    50  	// the time passed since beginning of execution.
    51  	FullTimestamp bool
    52  
    53  	// TimestampFormat to use for display when a full timestamp is printed
    54  	TimestampFormat string
    55  
    56  	// The fields are sorted by default for a consistent output. For applications
    57  	// that log extremely frequently and don't use the JSON formatter this may not
    58  	// be desired.
    59  	DisableSorting bool
    60  
    61  	// Redactions specifies sensitive strings that should be redacted (replaced
    62  	// with *'s) in all outputs.
    63  	Redactions []string
    64  }
    65  
    66  func (f *RedactedTextFormatter) Format(entry *logrus.Entry) ([]byte, error) {
    67  	var keys []string = make([]string, 0, len(entry.Data))
    68  	for k := range entry.Data {
    69  		keys = append(keys, k)
    70  	}
    71  
    72  	if !f.DisableSorting {
    73  		sort.Strings(keys)
    74  	}
    75  
    76  	b := &bytes.Buffer{}
    77  
    78  	prefixFieldClashes(entry.Data)
    79  
    80  	isColorTerminal := isTerminal && (runtime.GOOS != "windows")
    81  	isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors
    82  
    83  	timestampFormat := f.TimestampFormat
    84  	if timestampFormat == "" {
    85  		timestampFormat = time.RFC3339
    86  	}
    87  	if isColored {
    88  		f.printColored(b, entry, keys, timestampFormat)
    89  	} else {
    90  		if !f.DisableTimestamp {
    91  			f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
    92  		}
    93  		f.appendKeyValue(b, "level", entry.Level.String())
    94  		if entry.Message != "" {
    95  			f.appendKeyValue(b, "msg", entry.Message)
    96  		}
    97  		for _, key := range keys {
    98  			f.appendKeyValue(b, key, entry.Data[key])
    99  		}
   100  	}
   101  
   102  	//redact here
   103  	written := b.String()
   104  	b.Reset()
   105  	for _, redactStr := range f.Redactions {
   106  		written = Redact(written, redactStr)
   107  	}
   108  	b.WriteString(written)
   109  
   110  	b.WriteByte('\n')
   111  	return b.Bytes(), nil
   112  }
   113  
   114  func (f *RedactedTextFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry, keys []string, timestampFormat string) {
   115  	var levelColor int
   116  	switch entry.Level {
   117  	case logrus.DebugLevel:
   118  		levelColor = gray
   119  	case logrus.WarnLevel:
   120  		levelColor = yellow
   121  	case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
   122  		levelColor = red
   123  	default:
   124  		levelColor = blue
   125  	}
   126  
   127  	levelText := strings.ToUpper(entry.Level.String())[0:4]
   128  
   129  	if !f.FullTimestamp {
   130  		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
   131  	} else {
   132  		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
   133  	}
   134  	for _, k := range keys {
   135  		v := entry.Data[k]
   136  		fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%+v", levelColor, k, v)
   137  	}
   138  }
   139  
   140  func needsQuoting(text string) bool {
   141  	for _, ch := range text {
   142  		if !((ch >= 'a' && ch <= 'z') ||
   143  			(ch >= 'A' && ch <= 'Z') ||
   144  			(ch >= '0' && ch <= '9') ||
   145  			ch == '-' || ch == '.') {
   146  			return false
   147  		}
   148  	}
   149  	return true
   150  }
   151  
   152  func (f *RedactedTextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
   153  
   154  	b.WriteString(key)
   155  	b.WriteByte('=')
   156  
   157  	switch value := value.(type) {
   158  	case string:
   159  		if needsQuoting(value) {
   160  			b.WriteString(value)
   161  		} else {
   162  			fmt.Fprintf(b, "%q", value)
   163  		}
   164  	case error:
   165  		errmsg := value.Error()
   166  		if needsQuoting(errmsg) {
   167  			b.WriteString(errmsg)
   168  		} else {
   169  			fmt.Fprintf(b, "%q", value)
   170  		}
   171  	default:
   172  		fmt.Fprint(b, value)
   173  	}
   174  
   175  	b.WriteByte(' ')
   176  }
   177  
   178  // This is to not silently overwrite `time`, `msg` and `level` fields when
   179  // dumping it. If this code wasn't there doing:
   180  //
   181  //  logrus.WithField("level", 1).Info("hello")
   182  //
   183  // Would just silently drop the user provided level. Instead with this code
   184  // it'll logged as:
   185  //
   186  //  {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
   187  //
   188  // It's not exported because it's still using Data in an opinionated way. It's to
   189  // avoid code duplication between the two default formatters.
   190  func prefixFieldClashes(data logrus.Fields) {
   191  	_, ok := data["time"]
   192  	if ok {
   193  		data["fields.time"] = data["time"]
   194  	}
   195  
   196  	_, ok = data["msg"]
   197  	if ok {
   198  		data["fields.msg"] = data["msg"]
   199  	}
   200  
   201  	_, ok = data["level"]
   202  	if ok {
   203  		data["fields.level"] = data["level"]
   204  	}
   205  }
   206  
   207  func Redact(str, substr string) string {
   208  	return strings.Replace(str, substr, strings.Repeat("*", len(substr)), -1)
   209  }