github.com/crspeller/mattermost-server@v0.0.0-20190328001957-a200beb3d111/mlog/human/parser.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package human
     5  
     6  import (
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/crspeller/mattermost-server/mlog"
    16  )
    17  
    18  func ParseLogMessage(msg string) LogEntry {
    19  	result, err := parseLogMessage(msg)
    20  	if err != nil {
    21  		// If failed to parse, just output a LogEntry where all fields are blank, but Message is the original string
    22  		var result2 LogEntry
    23  		result2.Message = msg
    24  		return result2
    25  	}
    26  	return result
    27  }
    28  
    29  func parseLogMessage(msg string) (result LogEntry, err error) {
    30  
    31  	// Note: This implementation uses a custom json decoding loop.
    32  	// The primary advantage of this versus decoding directly into a map is to
    33  	// preserve the order of the fields. This can be simplified if we end up
    34  	// having the formatter sort fields alphabetically (logrus does by default)
    35  
    36  	dec := json.NewDecoder(strings.NewReader(msg))
    37  
    38  	// look for an initial "{"
    39  	if token, err := dec.Token(); err != nil {
    40  		return result, err
    41  	} else {
    42  		d, ok := token.(json.Delim)
    43  		if !ok || d != '{' {
    44  			return result, errors.New(fmt.Sprintf("input is not a JSON object, found: %v", token))
    45  		}
    46  	}
    47  
    48  	// read all key-value pairs
    49  	for dec.More() {
    50  		key, err := dec.Token()
    51  		if err != nil {
    52  			return result, err
    53  		}
    54  		if skey, ok := key.(string); !ok {
    55  			return result, errors.New("key is not a value string")
    56  		} else {
    57  			if !dec.More() {
    58  				return result, errors.New("missing value pair")
    59  			}
    60  
    61  			switch skey {
    62  			case "ts":
    63  				var ts json.Number
    64  				if err := dec.Decode(&ts); err != nil {
    65  					return result, err
    66  				}
    67  				if time, err := numberToTime(ts); err != nil {
    68  					return result, err
    69  				} else {
    70  					result.Time = time
    71  				}
    72  
    73  			case "level":
    74  				if s, err := decodeAsString(dec); err != nil {
    75  					return result, err
    76  				} else {
    77  					result.Level = s
    78  				}
    79  
    80  			case "msg":
    81  				if s, err := decodeAsString(dec); err != nil {
    82  					return result, err
    83  				} else {
    84  					result.Message = s
    85  				}
    86  
    87  			case "caller":
    88  				if s, err := decodeAsString(dec); err != nil {
    89  					return result, err
    90  				} else {
    91  					result.Caller = s
    92  				}
    93  
    94  			default:
    95  				var p interface{}
    96  				if err := dec.Decode(&p); err != nil {
    97  					return result, err
    98  				}
    99  				var f mlog.Field
   100  				f.Key = skey
   101  				f.Interface = p
   102  				result.Fields = append(result.Fields, f)
   103  			}
   104  		}
   105  	}
   106  
   107  	// read the "}"
   108  	if token, err := dec.Token(); err != nil {
   109  		return result, err
   110  	} else {
   111  		d, ok := token.(json.Delim)
   112  		if !ok || d != '}' {
   113  			return result, errors.New(fmt.Sprintf("failed to read '}', read: %v", token))
   114  		}
   115  	}
   116  
   117  	// make sure nothing else trailing
   118  	if token, err := dec.Token(); err != io.EOF {
   119  		return result, err
   120  	} else if token != nil {
   121  		return result, errors.New("found trailing data")
   122  	}
   123  
   124  	return result, nil
   125  }
   126  
   127  // Translate a number into a time
   128  func numberToTime(v json.Number) (time.Time, error) {
   129  	// Using floating point math to extract the nanoseconds leads to a time that doesn't exactly match the input
   130  	// Instead, parse out the components from the string representation
   131  
   132  	var t time.Time
   133  
   134  	// First make sure it is a number...
   135  	flt, err := v.Float64()
   136  	if err != nil {
   137  		return t, err
   138  	}
   139  
   140  	s := v.String()
   141  
   142  	if strings.ContainsAny(s, "eE") {
   143  		// input is in scientific notation. Convert to standard decimal notation
   144  		s = strconv.FormatFloat(flt, 'f', -1, 64)
   145  	}
   146  
   147  	// extract the seconds and nanoseconds separately
   148  	var nanos, sec int64
   149  
   150  	parts := strings.SplitN(s, ".", 2)
   151  	sec, err = strconv.ParseInt(parts[0], 10, 64)
   152  	if err != nil {
   153  		return t, err
   154  	}
   155  
   156  	if len(parts) == 2 {
   157  		nanosText := parts[1] + "000000000"
   158  		nanosText = nanosText[:9]
   159  		nanos, err = strconv.ParseInt(nanosText, 10, 64)
   160  		if err != nil {
   161  			return t, err
   162  		}
   163  	}
   164  
   165  	t = time.Unix(sec, nanos)
   166  	return t, nil
   167  }
   168  
   169  // Decodes a value from JSON, coercing it to a string value as necessary
   170  func decodeAsString(dec *json.Decoder) (s string, err error) {
   171  	var v interface{}
   172  	if err = dec.Decode(&v); err != nil {
   173  		return s, err
   174  	}
   175  	var ok bool
   176  	if s, ok = v.(string); ok {
   177  		return s, err
   178  	}
   179  	s = fmt.Sprint(v)
   180  	return s, err
   181  }