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