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  }