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