github.com/phuslu/log@v1.0.100/formatter.go (about) 1 package log 2 3 import ( 4 "reflect" 5 "strconv" 6 "unicode/utf16" 7 "unicode/utf8" 8 "unsafe" 9 ) 10 11 // FormatterArgs is a parsed sturct from json input 12 type FormatterArgs struct { 13 Time string // "2019-07-10T05:35:54.277Z" 14 Level string // "info" 15 Caller string // "prog.go:42" 16 Goid string // "123" 17 Stack string // "<stack string>" 18 Message string // "a structure message" 19 KeyValues []struct { 20 Key string // "foo" 21 Value string // "bar" 22 ValueType byte // 's' 23 } 24 } 25 26 // Get gets the value associated with the given key. 27 func (args *FormatterArgs) Get(key string) (value string) { 28 for i := len(args.KeyValues) - 1; i >= 0; i-- { 29 kv := &args.KeyValues[i] 30 if kv.Key == key { 31 value = kv.Value 32 break 33 } 34 } 35 return 36 } 37 38 func formatterArgsPos(key string) (pos int) { 39 switch key { 40 case "time": 41 pos = 1 42 case "level": 43 pos = 2 44 case "caller": 45 pos = 3 46 case "goid": 47 pos = 4 48 case "stack": 49 pos = 5 50 case "message", "msg": 51 pos = 6 52 } 53 return 54 } 55 56 // parseFormatterArgs extracts json string to json items 57 func parseFormatterArgs(json []byte, args *FormatterArgs) { 58 // treat formatter args as []string 59 const size = int(unsafe.Sizeof(FormatterArgs{}) / unsafe.Sizeof("")) 60 // nolint 61 slice := *(*[]string)(unsafe.Pointer(&reflect.SliceHeader{ 62 Data: uintptr(unsafe.Pointer(args)), Len: size, Cap: size, 63 })) 64 var keys = true 65 var key, str []byte 66 var ok bool 67 var typ byte 68 _ = json[len(json)-1] // remove bounds check 69 if json[0] != '{' { 70 return 71 } 72 for i := 1; i < len(json); i++ { 73 if keys { 74 if json[i] != '"' { 75 continue 76 } 77 i, str, _, ok = jsonParseString(json, i+1) 78 if !ok { 79 return 80 } 81 key = str[1 : len(str)-1] 82 } 83 for ; i < len(json); i++ { 84 if json[i] <= ' ' || json[i] == ',' || json[i] == ':' { 85 continue 86 } 87 break 88 } 89 i, typ, str, ok = jsonParseAny(json, i, true) 90 if !ok { 91 return 92 } 93 switch typ { 94 case 's': 95 str = str[1 : len(str)-1] 96 case 'S': 97 str = jsonUnescape(str[1:len(str)-1], str[:0]) 98 typ = 's' 99 } 100 pos := formatterArgsPos(b2s(key)) 101 if pos == 0 && args.Time == "" { 102 pos = 1 103 } 104 if pos != 0 { 105 if pos == 2 && len(str) != 0 && str[len(str)-1] == '\n' { 106 str = str[:len(str)-1] 107 } 108 if slice[pos-1] == "" { 109 slice[pos-1] = b2s(str) 110 } 111 } else { 112 args.KeyValues = append(args.KeyValues, struct { 113 Key, Value string 114 ValueType byte 115 }{b2s(key), b2s(str), typ}) 116 } 117 } 118 119 if args.Level == "" { 120 args.Level = "????" 121 } 122 } 123 124 func jsonParseString(json []byte, i int) (int, []byte, bool, bool) { 125 var s = i 126 _ = json[len(json)-1] // remove bounds check 127 for ; i < len(json); i++ { 128 if json[i] > '\\' { 129 continue 130 } 131 if json[i] == '"' { 132 return i + 1, json[s-1 : i+1], false, true 133 } 134 if json[i] == '\\' { 135 i++ 136 for ; i < len(json); i++ { 137 if json[i] > '\\' { 138 continue 139 } 140 if json[i] == '"' { 141 // look for an escaped slash 142 if json[i-1] == '\\' { 143 n := 0 144 for j := i - 2; j > 0; j-- { 145 if json[j] != '\\' { 146 break 147 } 148 n++ 149 } 150 if n%2 == 0 { 151 continue 152 } 153 } 154 return i + 1, json[s-1 : i+1], true, true 155 } 156 } 157 break 158 } 159 } 160 return i, json[s-1:], false, false 161 } 162 163 // jsonParseAny parses the next value from a json string. 164 // A Result is returned when the hit param is set. 165 // The return values are (i int, res Result, ok bool) 166 func jsonParseAny(json []byte, i int, hit bool) (int, byte, []byte, bool) { 167 var typ byte 168 var val []byte 169 _ = json[len(json)-1] // remove bounds check 170 for ; i < len(json); i++ { 171 if json[i] == '{' || json[i] == '[' { 172 i, val = jsonParseSquash(json, i) 173 if hit { 174 typ = 'o' 175 } 176 return i, typ, val, true 177 } 178 if json[i] <= ' ' { 179 continue 180 } 181 switch json[i] { 182 case '"': 183 i++ 184 var vesc bool 185 var ok bool 186 i, val, vesc, ok = jsonParseString(json, i) 187 typ = 's' 188 if !ok { 189 return i, typ, val, false 190 } 191 if hit && vesc { 192 typ = 'S' 193 } 194 return i, typ, val, true 195 case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 196 i, val = jsonParseNumber(json, i) 197 if hit { 198 typ = 'n' 199 } 200 return i, typ, val, true 201 case 't', 'f', 'n': 202 vc := json[i] 203 i, val = jsonParseLiteral(json, i) 204 if hit { 205 switch vc { 206 case 't': 207 typ = 't' 208 case 'f': 209 typ = 'f' 210 } 211 return i, typ, val, true 212 } 213 } 214 } 215 return i, typ, val, false 216 } 217 218 func jsonParseSquash(json []byte, i int) (int, []byte) { 219 // expects that the lead character is a '[' or '{' or '(' 220 // squash the value, ignoring all nested arrays and objects. 221 // the first '[' or '{' or '(' has already been read 222 s := i 223 i++ 224 depth := 1 225 _ = json[len(json)-1] // remove bounds check 226 for ; i < len(json); i++ { 227 if json[i] >= '"' && json[i] <= '}' { 228 switch json[i] { 229 case '"': 230 i++ 231 s2 := i 232 for ; i < len(json); i++ { 233 if json[i] > '\\' { 234 continue 235 } 236 if json[i] == '"' { 237 // look for an escaped slash 238 if json[i-1] == '\\' { 239 n := 0 240 for j := i - 2; j > s2-1; j-- { 241 if json[j] != '\\' { 242 break 243 } 244 n++ 245 } 246 if n%2 == 0 { 247 continue 248 } 249 } 250 break 251 } 252 } 253 case '{', '[', '(': 254 depth++ 255 case '}', ']', ')': 256 depth-- 257 if depth == 0 { 258 i++ 259 return i, json[s:i] 260 } 261 } 262 } 263 } 264 return i, json[s:] 265 } 266 267 func jsonParseNumber(json []byte, i int) (int, []byte) { 268 var s = i 269 i++ 270 _ = json[len(json)-1] // remove bounds check 271 for ; i < len(json); i++ { 272 if json[i] <= ' ' || json[i] == ',' || json[i] == ']' || 273 json[i] == '}' { 274 return i, json[s:i] 275 } 276 } 277 return i, json[s:] 278 } 279 280 func jsonParseLiteral(json []byte, i int) (int, []byte) { 281 var s = i 282 i++ 283 _ = json[len(json)-1] // remove bounds check 284 for ; i < len(json); i++ { 285 if json[i] < 'a' || json[i] > 'z' { 286 return i, json[s:i] 287 } 288 } 289 return i, json[s:] 290 } 291 292 // jsonUnescape unescapes a string 293 func jsonUnescape(json, str []byte) []byte { 294 _ = json[len(json)-1] // remove bounds check 295 var p [6]byte 296 for i := 0; i < len(json); i++ { 297 switch { 298 default: 299 str = append(str, json[i]) 300 case json[i] < ' ': 301 return str 302 case json[i] == '\\': 303 i++ 304 if i >= len(json) { 305 return str 306 } 307 switch json[i] { 308 default: 309 return str 310 case '\\': 311 str = append(str, '\\') 312 case '/': 313 str = append(str, '/') 314 case 'b': 315 str = append(str, '\b') 316 case 'f': 317 str = append(str, '\f') 318 case 'n': 319 str = append(str, '\n') 320 case 'r': 321 str = append(str, '\r') 322 case 't': 323 str = append(str, '\t') 324 case '"': 325 str = append(str, '"') 326 case 'u': 327 if i+5 > len(json) { 328 return str 329 } 330 m, _ := strconv.ParseUint(b2s(json[i+1:i+5]), 16, 64) 331 r := rune(m) 332 i += 5 333 if utf16.IsSurrogate(r) { 334 // need another code 335 if len(json[i:]) >= 6 && json[i] == '\\' && 336 json[i+1] == 'u' { 337 // we expect it to be correct so just consume it 338 m, _ = strconv.ParseUint(b2s(json[i+2:i+6]), 16, 64) 339 r = utf16.DecodeRune(r, rune(m)) 340 i += 6 341 } 342 } 343 str = append(str, p[:utf8.EncodeRune(p[:], r)]...) 344 i-- // backtrack index by one 345 } 346 } 347 } 348 return str 349 }