13 "golang.org/x/crypto/ssh/terminal"
26 baseTimestamp time.Time
30 baseTimestamp = time.Now()
33 // TextFormatter formats logs into text
34 type TextFormatter struct {
35 // Set to true to bypass checking for a TTY before outputting colors.
38 // Force disabling colors.
41 // Disable timestamp logging. useful when output is redirected to logging
42 // system that already adds timestamps.
45 // Enable logging the full timestamp when a TTY is attached instead of just
46 // the time passed since beginning of execution.
49 // TimestampFormat to use for display when a full timestamp is printed
50 TimestampFormat string
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
57 // QuoteEmptyFields will wrap empty fields in quotes if true
60 // Whether the logger's out is to a terminal
66 func (f *TextFormatter) init(entry *Entry) {
67 if entry.Logger != nil {
68 f.isTerminal = f.checkIfTerminal(entry.Logger.Out)
72 func (f *TextFormatter) checkIfTerminal(w io.Writer) bool {
73 switch v := w.(type) {
75 return terminal.IsTerminal(int(v.Fd()))
81 // Format renders a single log entry
82 func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
84 keys := make([]string, 0, len(entry.Data))
85 for k := range entry.Data {
86 keys = append(keys, k)
89 if !f.DisableSorting {
92 if entry.Buffer != nil {
98 prefixFieldClashes(entry.Data)
100 f.Do(func() { f.init(entry) })
102 isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors
104 timestampFormat := f.TimestampFormat
105 if timestampFormat == "" {
106 timestampFormat = defaultTimestampFormat
109 f.printColored(b, entry, keys, timestampFormat)
111 if !f.DisableTimestamp {
112 f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
114 f.appendKeyValue(b, "level", entry.Level.String())
115 if entry.Message != "" {
116 f.appendKeyValue(b, "msg", entry.Message)
118 for _, key := range keys {
119 f.appendKeyValue(b, key, entry.Data[key])
124 return b.Bytes(), nil
127 func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
134 case ErrorLevel, FatalLevel, PanicLevel:
140 levelText := strings.ToUpper(entry.Level.String())[0:4]
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)
147 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
149 for _, k := range keys {
151 fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
156 func (f *TextFormatter) needsQuoting(text string) bool {
157 if f.QuoteEmptyFields && len(text) == 0 {
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 == '+') {
171 func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
177 f.appendValue(b, value)
180 func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
181 stringVal, ok := value.(string)
183 stringVal = fmt.Sprint(value)
186 if !f.needsQuoting(stringVal) {
187 b.WriteString(stringVal)
189 b.WriteString(fmt.Sprintf("%q", stringVal))