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