summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/mattermost/logr/v2/formatter.go
blob: c8bb9b703d58d65fd3eea4a370a5810f6c2cd7a9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package logr

import (
	"bytes"
	"io"
	"runtime"
	"strconv"
)

// Formatter turns a LogRec into a formatted string.
type Formatter interface {
	// IsStacktraceNeeded returns true if this formatter requires a stacktrace to be
	// generated for each LogRecord. Enabling features such as `Caller` field require
	// a stacktrace.
	IsStacktraceNeeded() bool

	// Format converts a log record to bytes. If buf is not nil then it will be
	// be filled with the formatted results, otherwise a new buffer will be allocated.
	Format(rec *LogRec, level Level, buf *bytes.Buffer) (*bytes.Buffer, error)
}

const (
	// DefTimestampFormat is the default time stamp format used by Plain formatter and others.
	DefTimestampFormat = "2006-01-02 15:04:05.000 Z07:00"

	// TimestampMillisFormat is the format for logging milliseconds UTC
	TimestampMillisFormat = "Jan _2 15:04:05.000"
)

type Writer struct {
	io.Writer
}

func (w Writer) Writes(elems ...[]byte) (int, error) {
	var count int
	for _, e := range elems {
		if c, err := w.Write(e); err != nil {
			return count + c, err
		} else {
			count += c
		}
	}
	return count, nil
}

// DefaultFormatter is the default formatter, outputting only text with
// no colors and a space delimiter. Use `format.Plain` instead.
type DefaultFormatter struct {
}

// IsStacktraceNeeded always returns false for default formatter since the
// `Caller` field is not supported.
func (p *DefaultFormatter) IsStacktraceNeeded() bool {
	return false
}

// Format converts a log record to bytes.
func (p *DefaultFormatter) Format(rec *LogRec, level Level, buf *bytes.Buffer) (*bytes.Buffer, error) {
	if buf == nil {
		buf = &bytes.Buffer{}
	}
	timestampFmt := DefTimestampFormat

	buf.WriteString(rec.Time().Format(timestampFmt))
	buf.Write(Space)

	buf.WriteString(level.Name)
	buf.Write(Space)

	buf.WriteString(rec.Msg())
	buf.Write(Space)

	fields := rec.Fields()
	if len(fields) > 0 {
		if err := WriteFields(buf, fields, Space, NoColor); err != nil {
			return nil, err
		}
	}

	if level.Stacktrace {
		frames := rec.StackFrames()
		if len(frames) > 0 {
			buf.Write(Newline)
			if err := WriteStacktrace(buf, rec.StackFrames()); err != nil {
				return nil, err
			}
		}
	}
	buf.Write(Newline)

	return buf, nil
}

// WriteFields writes zero or more name value pairs to the io.Writer.
// The pairs output in key=value format with optional separator between fields.
func WriteFields(w io.Writer, fields []Field, separator []byte, color Color) error {
	ws := Writer{w}

	sep := []byte{}
	for _, field := range fields {
		if err := writeField(ws, field, sep, color); err != nil {
			return err
		}
		sep = separator
	}
	return nil
}

func writeField(ws Writer, field Field, sep []byte, color Color) error {
	if len(sep) != 0 {
		if _, err := ws.Write(sep); err != nil {
			return err
		}
	}
	if err := WriteWithColor(ws, field.Key, color); err != nil {
		return err
	}
	if _, err := ws.Write(Equals); err != nil {
		return err
	}
	return field.ValueString(ws, shouldQuote)
}

// shouldQuote returns true if val contains any characters that might be unsafe
// when injecting log output into an aggregator, viewer or report.
func shouldQuote(val string) bool {
	for _, c := range val {
		if !((c >= '0' && c <= '9') ||
			(c >= 'a' && c <= 'z') ||
			(c >= 'A' && c <= 'Z') ||
			c == '-' || c == '.' || c == '_' || c == '/' || c == '@' || c == '^' || c == '+') {
			return true
		}
	}
	return false
}

// WriteStacktrace formats and outputs a stack trace to an io.Writer.
func WriteStacktrace(w io.Writer, frames []runtime.Frame) error {
	ws := Writer{w}
	for _, frame := range frames {
		if frame.Function != "" {
			if _, err := ws.Writes(Space, Space, []byte(frame.Function), Newline); err != nil {
				return err
			}
		}
		if frame.File != "" {
			s := strconv.FormatInt(int64(frame.Line), 10)
			if _, err := ws.Writes([]byte{' ', ' ', ' ', ' ', ' ', ' '}, []byte(frame.File), Colon, []byte(s), Newline); err != nil {
				return err
			}
		}
	}
	return nil
}

// WriteWithColor outputs a string with the specified ANSI color.
func WriteWithColor(w io.Writer, s string, color Color) error {
	var err error

	writer := func(buf []byte) {
		if err != nil {
			return
		}
		_, err = w.Write(buf)
	}

	if color != NoColor {
		writer(AnsiColorPrefix)
		writer([]byte(strconv.FormatInt(int64(color), 10)))
		writer(AnsiColorSuffix)
	}

	if err == nil {
		_, err = io.WriteString(w, s)
	}

	if color != NoColor {
		writer(AnsiColorPrefix)
		writer([]byte(strconv.FormatInt(int64(NoColor), 10)))
		writer(AnsiColorSuffix)
	}
	return err
}