diff options
| author | Rutger Broekhoff | 2023-12-29 21:31:53 +0100 |
|---|---|---|
| committer | Rutger Broekhoff | 2023-12-29 21:31:53 +0100 |
| commit | 404aeae4545d2426c089a5f8d5e82dae56f5212b (patch) | |
| tree | 2d84e00af272b39fc04f3795ae06bc48970e57b5 /vendor/github.com/sirupsen/logrus/text_formatter.go | |
| parent | 209d8b0187ed025dec9ac149ebcced3462877bff (diff) | |
| download | gitolfs3-404aeae4545d2426c089a5f8d5e82dae56f5212b.tar.gz gitolfs3-404aeae4545d2426c089a5f8d5e82dae56f5212b.zip | |
Make Nix builds work
Diffstat (limited to 'vendor/github.com/sirupsen/logrus/text_formatter.go')
| -rw-r--r-- | vendor/github.com/sirupsen/logrus/text_formatter.go | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/vendor/github.com/sirupsen/logrus/text_formatter.go b/vendor/github.com/sirupsen/logrus/text_formatter.go new file mode 100644 index 0000000..be2c6ef --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/text_formatter.go | |||
| @@ -0,0 +1,339 @@ | |||
| 1 | package logrus | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "bytes" | ||
| 5 | "fmt" | ||
| 6 | "os" | ||
| 7 | "runtime" | ||
| 8 | "sort" | ||
| 9 | "strconv" | ||
| 10 | "strings" | ||
| 11 | "sync" | ||
| 12 | "time" | ||
| 13 | "unicode/utf8" | ||
| 14 | ) | ||
| 15 | |||
| 16 | const ( | ||
| 17 | red = 31 | ||
| 18 | yellow = 33 | ||
| 19 | blue = 36 | ||
| 20 | gray = 37 | ||
| 21 | ) | ||
| 22 | |||
| 23 | var baseTimestamp time.Time | ||
| 24 | |||
| 25 | func init() { | ||
| 26 | baseTimestamp = time.Now() | ||
| 27 | } | ||
| 28 | |||
| 29 | // TextFormatter formats logs into text | ||
| 30 | type TextFormatter struct { | ||
| 31 | // Set to true to bypass checking for a TTY before outputting colors. | ||
| 32 | ForceColors bool | ||
| 33 | |||
| 34 | // Force disabling colors. | ||
| 35 | DisableColors bool | ||
| 36 | |||
| 37 | // Force quoting of all values | ||
| 38 | ForceQuote bool | ||
| 39 | |||
| 40 | // DisableQuote disables quoting for all values. | ||
| 41 | // DisableQuote will have a lower priority than ForceQuote. | ||
| 42 | // If both of them are set to true, quote will be forced on all values. | ||
| 43 | DisableQuote bool | ||
| 44 | |||
| 45 | // Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/ | ||
| 46 | EnvironmentOverrideColors bool | ||
| 47 | |||
| 48 | // Disable timestamp logging. useful when output is redirected to logging | ||
| 49 | // system that already adds timestamps. | ||
| 50 | DisableTimestamp bool | ||
| 51 | |||
| 52 | // Enable logging the full timestamp when a TTY is attached instead of just | ||
| 53 | // the time passed since beginning of execution. | ||
| 54 | FullTimestamp bool | ||
| 55 | |||
| 56 | // TimestampFormat to use for display when a full timestamp is printed. | ||
| 57 | // The format to use is the same than for time.Format or time.Parse from the standard | ||
| 58 | // library. | ||
| 59 | // The standard Library already provides a set of predefined format. | ||
| 60 | TimestampFormat string | ||
| 61 | |||
| 62 | // The fields are sorted by default for a consistent output. For applications | ||
| 63 | // that log extremely frequently and don't use the JSON formatter this may not | ||
| 64 | // be desired. | ||
| 65 | DisableSorting bool | ||
| 66 | |||
| 67 | // The keys sorting function, when uninitialized it uses sort.Strings. | ||
| 68 | SortingFunc func([]string) | ||
| 69 | |||
| 70 | // Disables the truncation of the level text to 4 characters. | ||
| 71 | DisableLevelTruncation bool | ||
| 72 | |||
| 73 | // PadLevelText Adds padding the level text so that all the levels output at the same length | ||
| 74 | // PadLevelText is a superset of the DisableLevelTruncation option | ||
| 75 | PadLevelText bool | ||
| 76 | |||
| 77 | // QuoteEmptyFields will wrap empty fields in quotes if true | ||
| 78 | QuoteEmptyFields bool | ||
| 79 | |||
| 80 | // Whether the logger's out is to a terminal | ||
| 81 | isTerminal bool | ||
| 82 | |||
| 83 | // FieldMap allows users to customize the names of keys for default fields. | ||
| 84 | // As an example: | ||
| 85 | // formatter := &TextFormatter{ | ||
| 86 | // FieldMap: FieldMap{ | ||
| 87 | // FieldKeyTime: "@timestamp", | ||
| 88 | // FieldKeyLevel: "@level", | ||
| 89 | // FieldKeyMsg: "@message"}} | ||
| 90 | FieldMap FieldMap | ||
| 91 | |||
| 92 | // CallerPrettyfier can be set by the user to modify the content | ||
| 93 | // of the function and file keys in the data when ReportCaller is | ||
| 94 | // activated. If any of the returned value is the empty string the | ||
| 95 | // corresponding key will be removed from fields. | ||
| 96 | CallerPrettyfier func(*runtime.Frame) (function string, file string) | ||
| 97 | |||
| 98 | terminalInitOnce sync.Once | ||
| 99 | |||
| 100 | // The max length of the level text, generated dynamically on init | ||
| 101 | levelTextMaxLength int | ||
| 102 | } | ||
| 103 | |||
| 104 | func (f *TextFormatter) init(entry *Entry) { | ||
| 105 | if entry.Logger != nil { | ||
| 106 | f.isTerminal = checkIfTerminal(entry.Logger.Out) | ||
| 107 | } | ||
| 108 | // Get the max length of the level text | ||
| 109 | for _, level := range AllLevels { | ||
| 110 | levelTextLength := utf8.RuneCount([]byte(level.String())) | ||
| 111 | if levelTextLength > f.levelTextMaxLength { | ||
| 112 | f.levelTextMaxLength = levelTextLength | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | func (f *TextFormatter) isColored() bool { | ||
| 118 | isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows")) | ||
| 119 | |||
| 120 | if f.EnvironmentOverrideColors { | ||
| 121 | switch force, ok := os.LookupEnv("CLICOLOR_FORCE"); { | ||
| 122 | case ok && force != "0": | ||
| 123 | isColored = true | ||
| 124 | case ok && force == "0", os.Getenv("CLICOLOR") == "0": | ||
| 125 | isColored = false | ||
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 | return isColored && !f.DisableColors | ||
| 130 | } | ||
| 131 | |||
| 132 | // Format renders a single log entry | ||
| 133 | func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { | ||
| 134 | data := make(Fields) | ||
| 135 | for k, v := range entry.Data { | ||
| 136 | data[k] = v | ||
| 137 | } | ||
| 138 | prefixFieldClashes(data, f.FieldMap, entry.HasCaller()) | ||
| 139 | keys := make([]string, 0, len(data)) | ||
| 140 | for k := range data { | ||
| 141 | keys = append(keys, k) | ||
| 142 | } | ||
| 143 | |||
| 144 | var funcVal, fileVal string | ||
| 145 | |||
| 146 | fixedKeys := make([]string, 0, 4+len(data)) | ||
| 147 | if !f.DisableTimestamp { | ||
| 148 | fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime)) | ||
| 149 | } | ||
| 150 | fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel)) | ||
| 151 | if entry.Message != "" { | ||
| 152 | fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg)) | ||
| 153 | } | ||
| 154 | if entry.err != "" { | ||
| 155 | fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError)) | ||
| 156 | } | ||
| 157 | if entry.HasCaller() { | ||
| 158 | if f.CallerPrettyfier != nil { | ||
| 159 | funcVal, fileVal = f.CallerPrettyfier(entry.Caller) | ||
| 160 | } else { | ||
| 161 | funcVal = entry.Caller.Function | ||
| 162 | fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) | ||
| 163 | } | ||
| 164 | |||
| 165 | if funcVal != "" { | ||
| 166 | fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc)) | ||
| 167 | } | ||
| 168 | if fileVal != "" { | ||
| 169 | fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFile)) | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | if !f.DisableSorting { | ||
| 174 | if f.SortingFunc == nil { | ||
| 175 | sort.Strings(keys) | ||
| 176 | fixedKeys = append(fixedKeys, keys...) | ||
| 177 | } else { | ||
| 178 | if !f.isColored() { | ||
| 179 | fixedKeys = append(fixedKeys, keys...) | ||
| 180 | f.SortingFunc(fixedKeys) | ||
| 181 | } else { | ||
| 182 | f.SortingFunc(keys) | ||
| 183 | } | ||
| 184 | } | ||
| 185 | } else { | ||
| 186 | fixedKeys = append(fixedKeys, keys...) | ||
| 187 | } | ||
| 188 | |||
| 189 | var b *bytes.Buffer | ||
| 190 | if entry.Buffer != nil { | ||
| 191 | b = entry.Buffer | ||
| 192 | } else { | ||
| 193 | b = &bytes.Buffer{} | ||
| 194 | } | ||
| 195 | |||
| 196 | f.terminalInitOnce.Do(func() { f.init(entry) }) | ||
| 197 | |||
| 198 | timestampFormat := f.TimestampFormat | ||
| 199 | if timestampFormat == "" { | ||
| 200 | timestampFormat = defaultTimestampFormat | ||
| 201 | } | ||
| 202 | if f.isColored() { | ||
| 203 | f.printColored(b, entry, keys, data, timestampFormat) | ||
| 204 | } else { | ||
| 205 | |||
| 206 | for _, key := range fixedKeys { | ||
| 207 | var value interface{} | ||
| 208 | switch { | ||
| 209 | case key == f.FieldMap.resolve(FieldKeyTime): | ||
| 210 | value = entry.Time.Format(timestampFormat) | ||
| 211 | case key == f.FieldMap.resolve(FieldKeyLevel): | ||
| 212 | value = entry.Level.String() | ||
| 213 | case key == f.FieldMap.resolve(FieldKeyMsg): | ||
| 214 | value = entry.Message | ||
| 215 | case key == f.FieldMap.resolve(FieldKeyLogrusError): | ||
| 216 | value = entry.err | ||
| 217 | case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller(): | ||
| 218 | value = funcVal | ||
| 219 | case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller(): | ||
| 220 | value = fileVal | ||
| 221 | default: | ||
| 222 | value = data[key] | ||
| 223 | } | ||
| 224 | f.appendKeyValue(b, key, value) | ||
| 225 | } | ||
| 226 | } | ||
| 227 | |||
| 228 | b.WriteByte('\n') | ||
| 229 | return b.Bytes(), nil | ||
| 230 | } | ||
| 231 | |||
| 232 | func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) { | ||
| 233 | var levelColor int | ||
| 234 | switch entry.Level { | ||
| 235 | case DebugLevel, TraceLevel: | ||
| 236 | levelColor = gray | ||
| 237 | case WarnLevel: | ||
| 238 | levelColor = yellow | ||
| 239 | case ErrorLevel, FatalLevel, PanicLevel: | ||
| 240 | levelColor = red | ||
| 241 | case InfoLevel: | ||
| 242 | levelColor = blue | ||
| 243 | default: | ||
| 244 | levelColor = blue | ||
| 245 | } | ||
| 246 | |||
| 247 | levelText := strings.ToUpper(entry.Level.String()) | ||
| 248 | if !f.DisableLevelTruncation && !f.PadLevelText { | ||
| 249 | levelText = levelText[0:4] | ||
| 250 | } | ||
| 251 | if f.PadLevelText { | ||
| 252 | // Generates the format string used in the next line, for example "%-6s" or "%-7s". | ||
| 253 | // Based on the max level text length. | ||
| 254 | formatString := "%-" + strconv.Itoa(f.levelTextMaxLength) + "s" | ||
| 255 | // Formats the level text by appending spaces up to the max length, for example: | ||
| 256 | // - "INFO " | ||
| 257 | // - "WARNING" | ||
| 258 | levelText = fmt.Sprintf(formatString, levelText) | ||
| 259 | } | ||
| 260 | |||
| 261 | // Remove a single newline if it already exists in the message to keep | ||
| 262 | // the behavior of logrus text_formatter the same as the stdlib log package | ||
| 263 | entry.Message = strings.TrimSuffix(entry.Message, "\n") | ||
| 264 | |||
| 265 | caller := "" | ||
| 266 | if entry.HasCaller() { | ||
| 267 | funcVal := fmt.Sprintf("%s()", entry.Caller.Function) | ||
| 268 | fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) | ||
| 269 | |||
| 270 | if f.CallerPrettyfier != nil { | ||
| 271 | funcVal, fileVal = f.CallerPrettyfier(entry.Caller) | ||
| 272 | } | ||
| 273 | |||
| 274 | if fileVal == "" { | ||
| 275 | caller = funcVal | ||
| 276 | } else if funcVal == "" { | ||
| 277 | caller = fileVal | ||
| 278 | } else { | ||
| 279 | caller = fileVal + " " + funcVal | ||
| 280 | } | ||
| 281 | } | ||
| 282 | |||
| 283 | switch { | ||
| 284 | case f.DisableTimestamp: | ||
| 285 | fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message) | ||
| 286 | case !f.FullTimestamp: | ||
| 287 | fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message) | ||
| 288 | default: | ||
| 289 | fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message) | ||
| 290 | } | ||
| 291 | for _, k := range keys { | ||
| 292 | v := data[k] | ||
| 293 | fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k) | ||
| 294 | f.appendValue(b, v) | ||
| 295 | } | ||
| 296 | } | ||
| 297 | |||
| 298 | func (f *TextFormatter) needsQuoting(text string) bool { | ||
| 299 | if f.ForceQuote { | ||
| 300 | return true | ||
| 301 | } | ||
| 302 | if f.QuoteEmptyFields && len(text) == 0 { | ||
| 303 | return true | ||
| 304 | } | ||
| 305 | if f.DisableQuote { | ||
| 306 | return false | ||
| 307 | } | ||
| 308 | for _, ch := range text { | ||
| 309 | if !((ch >= 'a' && ch <= 'z') || | ||
| 310 | (ch >= 'A' && ch <= 'Z') || | ||
| 311 | (ch >= '0' && ch <= '9') || | ||
| 312 | ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') { | ||
| 313 | return true | ||
| 314 | } | ||
| 315 | } | ||
| 316 | return false | ||
| 317 | } | ||
| 318 | |||
| 319 | func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) { | ||
| 320 | if b.Len() > 0 { | ||
| 321 | b.WriteByte(' ') | ||
| 322 | } | ||
| 323 | b.WriteString(key) | ||
| 324 | b.WriteByte('=') | ||
| 325 | f.appendValue(b, value) | ||
| 326 | } | ||
| 327 | |||
| 328 | func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) { | ||
| 329 | stringVal, ok := value.(string) | ||
| 330 | if !ok { | ||
| 331 | stringVal = fmt.Sprint(value) | ||
| 332 | } | ||
| 333 | |||
| 334 | if !f.needsQuoting(stringVal) { | ||
| 335 | b.WriteString(stringVal) | ||
| 336 | } else { | ||
| 337 | b.WriteString(fmt.Sprintf("%q", stringVal)) | ||
| 338 | } | ||
| 339 | } | ||