github.com/keakon/golog@v0.0.0-20230330091222-cac71197c18d/formatter.go (about) 1 package golog 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 ) 8 9 var unknownFile = []byte("???") 10 11 var ( 12 // DefaultFormatter is the default formatter 13 DefaultFormatter = ParseFormat("[%l %D %T %s] %m") 14 // TimedRotatingFormatter is a formatter for TimedRotatingFileWriter 15 TimedRotatingFormatter = ParseFormat("[%l %T %s] %m") 16 ) 17 18 // A Formatter containing a sequence of FormatParts. 19 type Formatter struct { 20 formatParts []FormatPart 21 } 22 23 // ParseFormat parses a format string into a formatter. 24 func ParseFormat(format string) (formatter *Formatter) { 25 if format == "" { 26 return 27 } 28 formatter = &Formatter{} 29 formatter.findParts([]byte(format)) 30 formatter.appendByte('\n') 31 return 32 } 33 34 /* 35 Format formats a record to a bytes.Buffer. 36 Supported format directives: 37 38 %%: % 39 %l: short name of the level 40 %T: time string (HH:MM:SS) 41 %D: date string (YYYY-mm-DD) 42 %s: source code string (filename:line) 43 %S: full source code string (/path/filename.go:line) 44 */ 45 func (f *Formatter) Format(r *Record, buf *bytes.Buffer) { 46 for _, part := range f.formatParts { 47 part.Format(r, buf) 48 } 49 } 50 51 func (f *Formatter) findParts(format []byte) { 52 length := len(format) 53 index := bytes.IndexByte(format, '%') 54 if index == -1 || index == length-1 { 55 if length == 0 { 56 return 57 } 58 if length == 1 { 59 f.appendByte(format[0]) 60 } else { 61 f.appendBytes(format) 62 } 63 return 64 } 65 66 if index > 1 { 67 f.appendBytes(format[:index]) 68 } else if index == 1 { 69 f.appendByte(format[0]) 70 } 71 switch c := format[index+1]; c { 72 case '%': 73 f.appendByte('%') 74 case 'l': 75 f.formatParts = append(f.formatParts, &LevelFormatPart{}) 76 case 'T': 77 f.formatParts = append(f.formatParts, &TimeFormatPart{}) 78 case 'D': 79 f.formatParts = append(f.formatParts, &DateFormatPart{}) 80 case 's': 81 f.formatParts = append(f.formatParts, &SourceFormatPart{}) 82 case 'S': 83 f.formatParts = append(f.formatParts, &FullSourceFormatPart{}) 84 case 'm': 85 f.formatParts = append(f.formatParts, &MessageFormatPart{}) 86 default: 87 f.appendBytes([]byte{'%', c}) 88 } 89 f.findParts(format[index+2:]) 90 return 91 } 92 93 // FormatPart is an interface containing the Format() method. 94 type FormatPart interface { 95 Format(r *Record, buf *bytes.Buffer) 96 } 97 98 // ByteFormatPart is a FormatPart containing a byte. 99 type ByteFormatPart struct { 100 byte byte 101 } 102 103 // Format writes its byte to the buf. 104 func (p *ByteFormatPart) Format(r *Record, buf *bytes.Buffer) { 105 buf.WriteByte(p.byte) 106 } 107 108 // appendByte appends a byte to the formatter. 109 // If the previous FormatPart is a ByteFormatPart or BytesFormatPart, they will be merged into a BytesFormatPart; 110 // otherwise a new ByteFormatPart will be created. 111 func (f *Formatter) appendByte(b byte) { 112 parts := f.formatParts 113 count := len(parts) 114 if count == 0 { 115 f.formatParts = append(parts, &ByteFormatPart{byte: b}) 116 } else { 117 var p FormatPart 118 lastPart := parts[count-1] 119 switch lp := lastPart.(type) { 120 case *ByteFormatPart: 121 p = &BytesFormatPart{ 122 bytes: []byte{lp.byte, b}, 123 } 124 case *BytesFormatPart: 125 p = &BytesFormatPart{ 126 bytes: append(lp.bytes, b), 127 } 128 default: 129 p = &ByteFormatPart{byte: b} 130 f.formatParts = append(parts, p) 131 return 132 } 133 f.formatParts[count-1] = p 134 } 135 } 136 137 // BytesFormatPart is a FormatPart containing a byte slice. 138 type BytesFormatPart struct { 139 bytes []byte 140 } 141 142 // Format writes its bytes to the buf. 143 func (p *BytesFormatPart) Format(r *Record, buf *bytes.Buffer) { 144 buf.Write(p.bytes) 145 } 146 147 // appendBytes appends a byte slice to the formatter. 148 // If the previous FormatPart is a ByteFormatPart or BytesFormatPart, they will be merged into a BytesFormatPart; 149 // otherwise a new BytesFormatPart will be created. 150 func (f *Formatter) appendBytes(bs []byte) { 151 parts := f.formatParts 152 count := len(parts) 153 if count == 0 { 154 f.formatParts = append(parts, &BytesFormatPart{bytes: bs}) 155 } else { 156 var p FormatPart 157 lastPart := parts[count-1] 158 switch lp := lastPart.(type) { 159 case *ByteFormatPart: // won't reach here 160 p = &BytesFormatPart{ 161 bytes: append([]byte{lp.byte}, bs...), 162 } 163 case *BytesFormatPart: 164 p = &BytesFormatPart{ 165 bytes: append(lp.bytes, bs...), 166 } 167 default: 168 p = &BytesFormatPart{bytes: bs} 169 f.formatParts = append(parts, p) 170 return 171 } 172 f.formatParts[count-1] = p 173 } 174 } 175 176 // LevelFormatPart is a FormatPart of the level placeholder. 177 type LevelFormatPart struct{} 178 179 // Format writes the short level name of the record to the buf. 180 func (p *LevelFormatPart) Format(r *Record, buf *bytes.Buffer) { 181 buf.WriteByte(levelNames[int(r.level)]) 182 } 183 184 // TimeFormatPart is a FormatPart of the time placeholder. 185 type TimeFormatPart struct{} 186 187 // Format writes the time string of the record to the buf. 188 func (p *TimeFormatPart) Format(r *Record, buf *bytes.Buffer) { 189 if r.time == "" { 190 hour, min, sec := r.tm.Clock() 191 buf.Write(uint2Bytes2(hour)) 192 buf.WriteByte(':') 193 buf.Write(uint2Bytes2(min)) 194 buf.WriteByte(':') 195 buf.Write(uint2Bytes2(sec)) 196 } else { 197 buf.WriteString(r.time) 198 } 199 } 200 201 // DateFormatPart is a FormatPart of the date placeholder. 202 type DateFormatPart struct{} 203 204 // Format writes the date string of the record to the buf. 205 func (p *DateFormatPart) Format(r *Record, buf *bytes.Buffer) { 206 if r.date == "" { 207 year, mon, day := r.tm.Date() 208 buf.Write(uint2Bytes4(year)) 209 buf.WriteByte('-') 210 buf.Write(uint2Bytes2(int(mon))) 211 buf.WriteByte('-') 212 buf.Write(uint2Bytes2(day)) 213 } else { 214 buf.WriteString(r.date) 215 } 216 } 217 218 // SourceFormatPart is a FormatPart of the source code placeholder. 219 type SourceFormatPart struct{} 220 221 // Format writes the source file name and line number of the record to the buf. 222 func (p *SourceFormatPart) Format(r *Record, buf *bytes.Buffer) { 223 if r.line > 0 { 224 length := len(r.file) 225 if length > 0 { 226 start := 0 227 end := length 228 for i := length - 1; i >= 0; i-- { 229 c := r.file[i] 230 if os.IsPathSeparator(c) { 231 start = i + 1 232 break 233 } else if c == '.' { 234 end = i 235 } 236 } 237 buf.WriteString(r.file[start:end]) 238 buf.WriteByte(':') 239 buf.Write(fastUint2DynamicBytes(r.line)) 240 return 241 } 242 } 243 buf.Write(unknownFile) 244 } 245 246 // FullSourceFormatPart is a FormatPart of the full source code placeholder. 247 type FullSourceFormatPart struct{} 248 249 // Format writes the source file path and line number of the record to the buf. 250 func (p *FullSourceFormatPart) Format(r *Record, buf *bytes.Buffer) { 251 if r.line > 0 { 252 buf.WriteString(r.file) 253 buf.WriteByte(':') 254 buf.Write(fastUint2DynamicBytes(r.line)) 255 } else { 256 buf.Write(unknownFile) 257 } 258 } 259 260 // MessageFormatPart is a FormatPart of the message placeholder. 261 type MessageFormatPart struct{} 262 263 // Format writes the formatted message with args to the buf. 264 func (p *MessageFormatPart) Format(r *Record, buf *bytes.Buffer) { 265 if len(r.args) > 0 { 266 if r.message == "" { 267 fmt.Fprint(buf, r.args...) 268 } else { 269 fmt.Fprintf(buf, r.message, r.args...) 270 } 271 } else if r.message != "" { 272 buf.WriteString(r.message) 273 } 274 }