podman

Форк
0
339 строк · 9.0 Кб
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
}
340

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.