1 package log
2
3 import (
4 "bytes"
5 "fmt"
6 "runtime"
7 "sort"
8 "strings"
9 "time"
10
11 "github.com/sirupsen/logrus"
12 )
13
14 func newFormatter(noColors bool) *Formatter {
15 return &Formatter{
16 HideKeys: false,
17 FieldsOrder: []string{
18 "source", "metric",
19 "sink", "db", "filename", "address",
20 "rows", "sql", "params"},
21 TimestampFormat: "2006-01-02 15:04:05.000",
22 ShowFullLevel: true,
23 NoColors: noColors,
24 }
25 }
26
27
28
29 type Formatter struct {
30
31 FieldsOrder []string
32
33
34 TimestampFormat string
35
36
37 HideKeys bool
38
39
40 NoColors bool
41
42
43 NoFieldsColors bool
44
45
46 NoFieldsSpace bool
47
48
49 ShowFullLevel bool
50
51
52 NoUppercaseLevel bool
53
54
55 TrimMessages bool
56
57
58 CallerFirst bool
59
60
61 CustomCallerFormatter func(*runtime.Frame) string
62 }
63
64
65 func (f *Formatter) Format(entry *logrus.Entry) ([]byte, error) {
66 levelColor := getColorByLevel(entry.Level)
67
68 timestampFormat := f.TimestampFormat
69 if timestampFormat == "" {
70 timestampFormat = time.StampMilli
71 }
72
73
74 b := &bytes.Buffer{}
75
76
77 b.WriteString(entry.Time.Format(timestampFormat))
78
79
80 var level string
81 if f.NoUppercaseLevel {
82 level = entry.Level.String()
83 } else {
84 level = strings.ToUpper(entry.Level.String())
85 }
86
87 if f.CallerFirst {
88 f.writeCaller(b, entry)
89 }
90
91 if !f.NoColors {
92 fmt.Fprintf(b, "\x1b[%dm", levelColor)
93 }
94
95 b.WriteString(" [")
96 if f.ShowFullLevel {
97 b.WriteString(level)
98 } else {
99 b.WriteString(level[:4])
100 }
101 b.WriteString("]")
102
103 if !f.NoFieldsSpace {
104 b.WriteString(" ")
105 }
106
107 if !f.NoColors && f.NoFieldsColors {
108 b.WriteString("\x1b[0m")
109 }
110
111
112 if f.FieldsOrder == nil {
113 f.writeFields(b, entry)
114 } else {
115 f.writeOrderedFields(b, entry)
116 }
117
118 if f.NoFieldsSpace {
119 b.WriteString(" ")
120 }
121
122 if !f.NoColors && !f.NoFieldsColors {
123 b.WriteString("\x1b[0m")
124 }
125
126
127 if f.TrimMessages {
128 b.WriteString(strings.TrimSpace(entry.Message))
129 } else {
130 b.WriteString(entry.Message)
131 }
132
133 if !f.CallerFirst {
134 f.writeCaller(b, entry)
135 }
136
137 b.WriteByte('\n')
138
139 return b.Bytes(), nil
140 }
141
142 func trimFilename(s string) string {
143 const sub = "internal/"
144 idx := strings.Index(s, sub)
145 if idx == -1 {
146 return s
147 }
148 return s[idx+len(sub):]
149 }
150
151 func (f *Formatter) writeCaller(b *bytes.Buffer, entry *logrus.Entry) {
152 if entry.HasCaller() {
153 if f.CustomCallerFormatter != nil {
154 fmt.Fprint(b, f.CustomCallerFormatter(entry.Caller))
155 } else {
156 if strings.Contains(entry.Caller.Function, "PgxLogger") {
157 return
158 }
159 fmt.Fprintf(
160 b,
161 " (%s:%d %s)",
162 trimFilename(entry.Caller.File),
163 entry.Caller.Line,
164 trimFilename(entry.Caller.Function),
165 )
166 }
167 }
168 }
169
170 func (f *Formatter) writeFields(b *bytes.Buffer, entry *logrus.Entry) {
171 if len(entry.Data) != 0 {
172 fields := make([]string, 0, len(entry.Data))
173 for field := range entry.Data {
174 fields = append(fields, field)
175 }
176
177 sort.Strings(fields)
178
179 for _, field := range fields {
180 f.writeField(b, entry, field)
181 }
182 }
183 }
184
185 func (f *Formatter) writeOrderedFields(b *bytes.Buffer, entry *logrus.Entry) {
186 length := len(entry.Data)
187 foundFieldsMap := map[string]bool{}
188 for _, field := range f.FieldsOrder {
189 if _, ok := entry.Data[field]; ok {
190 foundFieldsMap[field] = true
191 length--
192 f.writeField(b, entry, field)
193 }
194 }
195
196 if length > 0 {
197 notFoundFields := make([]string, 0, length)
198 for field := range entry.Data {
199 if !foundFieldsMap[field] {
200 notFoundFields = append(notFoundFields, field)
201 }
202 }
203
204 sort.Strings(notFoundFields)
205
206 for _, field := range notFoundFields {
207 f.writeField(b, entry, field)
208 }
209 }
210 }
211
212 func (f *Formatter) writeField(b *bytes.Buffer, entry *logrus.Entry, field string) {
213 if f.HideKeys {
214 fmt.Fprintf(b, "[%v]", entry.Data[field])
215 } else {
216 fmt.Fprintf(b, "[%s:%v]", field, entry.Data[field])
217 }
218
219 if !f.NoFieldsSpace {
220 b.WriteString(" ")
221 }
222 }
223
224 const (
225 colorRed = 31
226 colorBlue = 36
227 colorMagenta = 35
228 colorGreen = 32
229
230
231 )
232
233 func getColorByLevel(level logrus.Level) int {
234 switch level {
235 case logrus.DebugLevel, logrus.TraceLevel:
236 return colorBlue
237 case logrus.WarnLevel:
238 return colorMagenta
239 case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
240 return colorRed
241 default:
242 return colorGreen
243 }
244 }
245