1package logrus 2 3import ( 4 "bytes" 5 "fmt" 6 "runtime" 7 "sort" 8 "strings" 9 "time" 10) 11 12const ( 13 nocolor = 0 14 red = 31 15 green = 32 16 yellow = 33 17 blue = 34 18 gray = 37 19) 20 21var ( 22 baseTimestamp time.Time 23 isTerminal bool 24) 25 26func init() { 27 baseTimestamp = time.Now() 28 isTerminal = IsTerminal() 29} 30 31func miniTS() int { 32 return int(time.Since(baseTimestamp) / time.Second) 33} 34 35type TextFormatter struct { 36 // Set to true to bypass checking for a TTY before outputting colors. 37 ForceColors bool 38 39 // Force disabling colors. 40 DisableColors bool 41 42 // Disable timestamp logging. useful when output is redirected to logging 43 // system that already adds timestamps. 44 DisableTimestamp bool 45 46 // Enable logging the full timestamp when a TTY is attached instead of just 47 // the time passed since beginning of execution. 48 FullTimestamp bool 49 50 // TimestampFormat to use for display when a full timestamp is printed 51 TimestampFormat string 52 53 // The fields are sorted by default for a consistent output. For applications 54 // that log extremely frequently and don't use the JSON formatter this may not 55 // be desired. 56 DisableSorting bool 57} 58 59func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { 60 var b *bytes.Buffer 61 var keys []string = make([]string, 0, len(entry.Data)) 62 for k := range entry.Data { 63 keys = append(keys, k) 64 } 65 66 if !f.DisableSorting { 67 sort.Strings(keys) 68 } 69 if entry.Buffer != nil { 70 b = entry.Buffer 71 } else { 72 b = &bytes.Buffer{} 73 } 74 75 prefixFieldClashes(entry.Data) 76 77 isColorTerminal := isTerminal && (runtime.GOOS != "windows") 78 isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors 79 80 timestampFormat := f.TimestampFormat 81 if timestampFormat == "" { 82 timestampFormat = DefaultTimestampFormat 83 } 84 if isColored { 85 f.printColored(b, entry, keys, timestampFormat) 86 } else { 87 if !f.DisableTimestamp { 88 f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat)) 89 } 90 f.appendKeyValue(b, "level", entry.Level.String()) 91 if entry.Message != "" { 92 f.appendKeyValue(b, "msg", entry.Message) 93 } 94 for _, key := range keys { 95 f.appendKeyValue(b, key, entry.Data[key]) 96 } 97 } 98 99 b.WriteByte('\n') 100 return b.Bytes(), nil 101} 102 103func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) { 104 var levelColor int 105 switch entry.Level { 106 case DebugLevel: 107 levelColor = gray 108 case WarnLevel: 109 levelColor = yellow 110 case ErrorLevel, FatalLevel, PanicLevel: 111 levelColor = red 112 default: 113 levelColor = blue 114 } 115 116 levelText := strings.ToUpper(entry.Level.String())[0:4] 117 118 if !f.FullTimestamp { 119 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message) 120 } else { 121 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message) 122 } 123 for _, k := range keys { 124 v := entry.Data[k] 125 fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k) 126 f.appendValue(b, v) 127 } 128} 129 130func needsQuoting(text string) bool { 131 for _, ch := range text { 132 if !((ch >= 'a' && ch <= 'z') || 133 (ch >= 'A' && ch <= 'Z') || 134 (ch >= '0' && ch <= '9') || 135 ch == '-' || ch == '.') { 136 return true 137 } 138 } 139 return false 140} 141 142func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) { 143 144 b.WriteString(key) 145 b.WriteByte('=') 146 f.appendValue(b, value) 147 b.WriteByte(' ') 148} 149 150func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) { 151 switch value := value.(type) { 152 case string: 153 if !needsQuoting(value) { 154 b.WriteString(value) 155 } else { 156 fmt.Fprintf(b, "%q", value) 157 } 158 case error: 159 errmsg := value.Error() 160 if !needsQuoting(errmsg) { 161 b.WriteString(errmsg) 162 } else { 163 fmt.Fprintf(b, "%q", errmsg) 164 } 165 default: 166 fmt.Fprint(b, value) 167 } 168} 169