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