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  }