-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathtext_formatter.go
228 lines (203 loc) · 7.24 KB
/
text_formatter.go
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
// Copyright 2021 The ZKits Project Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"bytes"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/edoger/zkits-logger/internal"
)
// This regular expression is used to analyze placeholders in text formatter format.
var formatRegexp = regexp.MustCompile(`{(name|time|level|message|caller|stack|fields)(?:@?([^{}]*)?)?}`)
// The default text formatter.
var defaultTextFormatter = MustNewTextFormatter("{name}:[{time}][{level@sc}] {message}{caller}{fields}{stack}", false)
// The default quote text formatter.
var defaultQuoteTextFormatter = MustNewTextFormatter("{name}:[{time}][{level@sc}] {message}{caller}{fields}{stack}", true)
// DefaultTextFormatter returns the default text formatter.
func DefaultTextFormatter() Formatter {
return defaultTextFormatter
}
// DefaultQuoteTextFormatter returns the default quote text formatter.
func DefaultQuoteTextFormatter() Formatter {
return defaultQuoteTextFormatter
}
// NewTextFormatter creates and returns an instance of the log text formatter.
// The format parameter is used to control the format of the log, and it has many control parameters.
// For example:
// "[{name}][{time}][{level}] {caller} {message} {fields}"
// The supported placeholders are:
// {name} The name of the logger that recorded this log.
// {time} Record the time of this log.
// {level} The level of this log.
// {caller} The name and line number of the file where this log was generated. (If enabled)
// {message} The message of this log.
// {fields} The extended fields of this log. (if it exists)
// {stack} The call stack of this log. (if it exists)
// It is worth knowing:
// 1. For the {time} parameter, we can specify time format, like this: {time@2006-01-02 15:04:05}.
// 2. For the {level} parameter, we can specify level format, like this: {level@sc},
// {level@sc} or {level@cs} will call the Level.ShortCapitalString method.
// {level@s} will call the Level.ShortString method.
// {level@c} will call the Level.CapitalString method.
// For other will call the Level.String method.
// 3. Considering the aesthetics of the format, for {caller} and {fields} and {stack}, if
// there is non-empty data, a space will be automatically added in front.
// If this behavior is not needed, use {caller@?} or {fields@?} or {stack@?} parameters.
// The quote parameter is used to escape invisible characters in the log.
func NewTextFormatter(format string, quote bool) (Formatter, error) {
sub := formatRegexp.FindAllStringSubmatch(format, -1)
if len(sub) == 0 {
return nil, fmt.Errorf("invalid text formatter format %q", format)
}
// If sub is not empty, then idx is definitely not empty.
idx := formatRegexp.FindAllStringIndex(format, -1)
f := &textFormatter{quote: quote, callerPrefix: " ", fieldsPrefix: " ", stackPrefix: " "}
var parts []string
var start int
for i, j := 0, len(sub); i < j; i++ {
key, args := sub[i][1], sub[i][2]
switch key {
case "name":
f.encoders = append(f.encoders, f.encodeName)
case "time":
if args == "" {
f.encoders = append(f.encoders, f.encodeTime)
} else {
f.timeFormat = args
f.encoders = append(f.encoders, f.encodeTimeWithFormat)
}
case "level":
switch args {
case "cs", "sc":
f.encoders = append(f.encoders, f.encodeShortCapitalLevel)
case "s":
f.encoders = append(f.encoders, f.encodeShortLevel)
case "c":
f.encoders = append(f.encoders, f.encodeCapitalLevel)
default:
f.encoders = append(f.encoders, f.encodeLevel)
}
case "message":
f.encoders = append(f.encoders, f.encodeMessage)
case "caller":
f.encoders = append(f.encoders, f.encodeCaller)
if args == "?" {
f.callerPrefix = ""
}
case "fields":
f.encoders = append(f.encoders, f.encodeFields)
if args == "?" {
f.fieldsPrefix = ""
}
case "stack":
f.encoders = append(f.encoders, f.encodeStack)
if args == "?" {
f.stackPrefix = ""
}
}
parts = append(parts, format[start:idx[i][0]])
start = idx[i][1]
}
f.format = strings.Join(append(parts, format[start:]), "%s")
return f, nil
}
// MustNewTextFormatter is like NewTextFormatter, but triggers a panic when an error occurs.
func MustNewTextFormatter(format string, quote bool) Formatter {
f, err := NewTextFormatter(format, quote)
if err != nil {
panic(err)
}
return f
}
// The built-in text formatter.
type textFormatter struct {
format string
quote bool
encoders []func(Entity) string
timeFormat string
callerPrefix string
fieldsPrefix string
stackPrefix string
}
// Format formats the given log entity into character data and writes it to the given buffer.
func (f *textFormatter) Format(e Entity, b *bytes.Buffer) (err error) {
args := make([]interface{}, len(f.encoders))
for i, j := 0, len(f.encoders); i < j; i++ {
args[i] = f.encoders[i](e)
}
if f.quote {
// The quoted[0] and quoted[len(s)-1] is '"', they need to be removed.
quoted := strconv.AppendQuote(nil, fmt.Sprintf(f.format, args...))
quoted[len(quoted)-1] = '\n'
_, err = b.Write(quoted[1:])
} else {
_, err = b.WriteString(fmt.Sprintf(f.format, args...) + "\n")
}
return
}
// Encode the name of the log.
func (f *textFormatter) encodeName(e Entity) string {
return e.Name()
}
// Encode the level of the log.
func (f *textFormatter) encodeLevel(e Entity) string {
return e.Level().String()
}
// Encode the short capital level of the log.
func (f *textFormatter) encodeShortCapitalLevel(e Entity) string {
return e.Level().ShortCapitalString()
}
// Encode the short level of the log.
func (f *textFormatter) encodeShortLevel(e Entity) string {
return e.Level().ShortString()
}
// Encode the capital level of the log.
func (f *textFormatter) encodeCapitalLevel(e Entity) string {
return e.Level().CapitalString()
}
// Encode the time of the log.
func (f *textFormatter) encodeTime(e Entity) string {
return e.TimeString()
}
// Encode the time with format of the log.
func (f *textFormatter) encodeTimeWithFormat(e Entity) string {
return e.Time().Format(f.timeFormat)
}
// Encode the caller of the log.
func (f *textFormatter) encodeCaller(e Entity) string {
if s := e.Caller(); s != "" {
return f.callerPrefix + s
}
return ""
}
// Encode the message of the log.
func (f *textFormatter) encodeMessage(e Entity) string {
return e.Message()
}
// Encode the fields of the log.
func (f *textFormatter) encodeFields(e Entity) string {
if fields := e.Fields(); len(fields) > 0 {
return f.fieldsPrefix + internal.FormatFieldsToText(e.Fields())
}
return ""
}
// Encode the stack of the log.
func (f *textFormatter) encodeStack(e Entity) string {
if stack := e.Stack(); len(stack) > 0 {
return f.stackPrefix + strings.Join(stack, "; ")
}
return ""
}