...

Source file src/github.com/sirupsen/logrus/text_formatter.go

Documentation: github.com/sirupsen/logrus

     1  package logrus
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os"
     7  	"runtime"
     8  	"sort"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  )
    13  
    14  const (
    15  	nocolor = 0
    16  	red     = 31
    17  	green   = 32
    18  	yellow  = 33
    19  	blue    = 36
    20  	gray    = 37
    21  )
    22  
    23  var (
    24  	baseTimestamp time.Time
    25  	emptyFieldMap FieldMap
    26  )
    27  
    28  func init() {
    29  	baseTimestamp = time.Now()
    30  }
    31  
    32  // TextFormatter formats logs into text
    33  type TextFormatter struct {
    34  	// Set to true to bypass checking for a TTY before outputting colors.
    35  	ForceColors bool
    36  
    37  	// Force disabling colors.
    38  	DisableColors bool
    39  
    40  	// Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/
    41  	EnvironmentOverrideColors bool
    42  
    43  	// Disable timestamp logging. useful when output is redirected to logging
    44  	// system that already adds timestamps.
    45  	DisableTimestamp bool
    46  
    47  	// Enable logging the full timestamp when a TTY is attached instead of just
    48  	// the time passed since beginning of execution.
    49  	FullTimestamp bool
    50  
    51  	// TimestampFormat to use for display when a full timestamp is printed
    52  	TimestampFormat string
    53  
    54  	// The fields are sorted by default for a consistent output. For applications
    55  	// that log extremely frequently and don't use the JSON formatter this may not
    56  	// be desired.
    57  	DisableSorting bool
    58  
    59  	// The keys sorting function, when uninitialized it uses sort.Strings.
    60  	SortingFunc func([]string)
    61  
    62  	// Disables the truncation of the level text to 4 characters.
    63  	DisableLevelTruncation bool
    64  
    65  	// QuoteEmptyFields will wrap empty fields in quotes if true
    66  	QuoteEmptyFields bool
    67  
    68  	// Whether the logger's out is to a terminal
    69  	isTerminal bool
    70  
    71  	// FieldMap allows users to customize the names of keys for default fields.
    72  	// As an example:
    73  	// formatter := &TextFormatter{
    74  	//     FieldMap: FieldMap{
    75  	//         FieldKeyTime:  "@timestamp",
    76  	//         FieldKeyLevel: "@level",
    77  	//         FieldKeyMsg:   "@message"}}
    78  	FieldMap FieldMap
    79  
    80  	terminalInitOnce sync.Once
    81  }
    82  
    83  func (f *TextFormatter) init(entry *Entry) {
    84  	if entry.Logger != nil {
    85  		f.isTerminal = checkIfTerminal(entry.Logger.Out)
    86  
    87  		if f.isTerminal {
    88  			initTerminal(entry.Logger.Out)
    89  		}
    90  	}
    91  }
    92  
    93  func (f *TextFormatter) isColored() bool {
    94  	isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows"))
    95  
    96  	if f.EnvironmentOverrideColors {
    97  		if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok && force != "0" {
    98  			isColored = true
    99  		} else if ok && force == "0" {
   100  			isColored = false
   101  		} else if os.Getenv("CLICOLOR") == "0" {
   102  			isColored = false
   103  		}
   104  	}
   105  
   106  	return isColored && !f.DisableColors
   107  }
   108  
   109  // Format renders a single log entry
   110  func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
   111  	data := make(Fields)
   112  	for k, v := range entry.Data {
   113  		data[k] = v
   114  	}
   115  	prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
   116  	keys := make([]string, 0, len(data))
   117  	for k := range data {
   118  		keys = append(keys, k)
   119  	}
   120  
   121  	fixedKeys := make([]string, 0, 4+len(data))
   122  	if !f.DisableTimestamp {
   123  		fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
   124  	}
   125  	fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel))
   126  	if entry.Message != "" {
   127  		fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg))
   128  	}
   129  	if entry.err != "" {
   130  		fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError))
   131  	}
   132  	if entry.HasCaller() {
   133  		fixedKeys = append(fixedKeys,
   134  			f.FieldMap.resolve(FieldKeyFunc), f.FieldMap.resolve(FieldKeyFile))
   135  	}
   136  
   137  	if !f.DisableSorting {
   138  		if f.SortingFunc == nil {
   139  			sort.Strings(keys)
   140  			fixedKeys = append(fixedKeys, keys...)
   141  		} else {
   142  			if !f.isColored() {
   143  				fixedKeys = append(fixedKeys, keys...)
   144  				f.SortingFunc(fixedKeys)
   145  			} else {
   146  				f.SortingFunc(keys)
   147  			}
   148  		}
   149  	} else {
   150  		fixedKeys = append(fixedKeys, keys...)
   151  	}
   152  
   153  	var b *bytes.Buffer
   154  	if entry.Buffer != nil {
   155  		b = entry.Buffer
   156  	} else {
   157  		b = &bytes.Buffer{}
   158  	}
   159  
   160  	f.terminalInitOnce.Do(func() { f.init(entry) })
   161  
   162  	timestampFormat := f.TimestampFormat
   163  	if timestampFormat == "" {
   164  		timestampFormat = defaultTimestampFormat
   165  	}
   166  	if f.isColored() {
   167  		f.printColored(b, entry, keys, data, timestampFormat)
   168  	} else {
   169  		for _, key := range fixedKeys {
   170  			var value interface{}
   171  			switch {
   172  			case key == f.FieldMap.resolve(FieldKeyTime):
   173  				value = entry.Time.Format(timestampFormat)
   174  			case key == f.FieldMap.resolve(FieldKeyLevel):
   175  				value = entry.Level.String()
   176  			case key == f.FieldMap.resolve(FieldKeyMsg):
   177  				value = entry.Message
   178  			case key == f.FieldMap.resolve(FieldKeyLogrusError):
   179  				value = entry.err
   180  			case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller():
   181  				value = entry.Caller.Function
   182  			case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller():
   183  				value = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
   184  			default:
   185  				value = data[key]
   186  			}
   187  			f.appendKeyValue(b, key, value)
   188  		}
   189  	}
   190  
   191  	b.WriteByte('\n')
   192  	return b.Bytes(), nil
   193  }
   194  
   195  func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) {
   196  	var levelColor int
   197  	switch entry.Level {
   198  	case DebugLevel, TraceLevel:
   199  		levelColor = gray
   200  	case WarnLevel:
   201  		levelColor = yellow
   202  	case ErrorLevel, FatalLevel, PanicLevel:
   203  		levelColor = red
   204  	default:
   205  		levelColor = blue
   206  	}
   207  
   208  	levelText := strings.ToUpper(entry.Level.String())
   209  	if !f.DisableLevelTruncation {
   210  		levelText = levelText[0:4]
   211  	}
   212  
   213  	// Remove a single newline if it already exists in the message to keep
   214  	// the behavior of logrus text_formatter the same as the stdlib log package
   215  	entry.Message = strings.TrimSuffix(entry.Message, "\n")
   216  
   217  	caller := ""
   218  
   219  	if entry.HasCaller() {
   220  		caller = fmt.Sprintf("%s:%d %s()",
   221  			entry.Caller.File, entry.Caller.Line, entry.Caller.Function)
   222  	}
   223  
   224  	if f.DisableTimestamp {
   225  		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message)
   226  	} else if !f.FullTimestamp {
   227  		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message)
   228  	} else {
   229  		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message)
   230  	}
   231  	for _, k := range keys {
   232  		v := data[k]
   233  		fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
   234  		f.appendValue(b, v)
   235  	}
   236  }
   237  
   238  func (f *TextFormatter) needsQuoting(text string) bool {
   239  	if f.QuoteEmptyFields && len(text) == 0 {
   240  		return true
   241  	}
   242  	for _, ch := range text {
   243  		if !((ch >= 'a' && ch <= 'z') ||
   244  			(ch >= 'A' && ch <= 'Z') ||
   245  			(ch >= '0' && ch <= '9') ||
   246  			ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
   247  			return true
   248  		}
   249  	}
   250  	return false
   251  }
   252  
   253  func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
   254  	if b.Len() > 0 {
   255  		b.WriteByte(' ')
   256  	}
   257  	b.WriteString(key)
   258  	b.WriteByte('=')
   259  	f.appendValue(b, value)
   260  }
   261  
   262  func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
   263  	stringVal, ok := value.(string)
   264  	if !ok {
   265  		stringVal = fmt.Sprint(value)
   266  	}
   267  
   268  	if !f.needsQuoting(stringVal) {
   269  		b.WriteString(stringVal)
   270  	} else {
   271  		b.WriteString(fmt.Sprintf("%q", stringVal))
   272  	}
   273  }
   274  

View as plain text