github.com/honeycombio/honeytail@v1.9.0/parsers/mongodb/logparser/log_line.go (about)

     1  package logparser
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"math"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  	"unicode"
    12  )
    13  
    14  const (
    15  	endRune rune = 1114112
    16  )
    17  
    18  type partialLogLineError struct {
    19  	InnerError error
    20  }
    21  
    22  func (p partialLogLineError) Error() string {
    23  	return fmt.Sprintf("Partial log line: %v", p.InnerError)
    24  }
    25  
    26  func IsPartialLogLine(err error) bool {
    27  	_, ok := err.(partialLogLineError)
    28  	return ok
    29  }
    30  
    31  func ParseLogLine(input string) (map[string]interface{}, error) {
    32  	p := LogLineParser{Buffer: input}
    33  	p.Init()
    34  	if err := p.Parse(); err != nil {
    35  		return nil, err
    36  	}
    37  	return p.Fields, nil
    38  }
    39  
    40  func ParseQuery(query string) (map[string]interface{}, error) {
    41  	p := LogLineParser{Buffer: query}
    42  	p.Init()
    43  	rv, err := p.parseFieldValue("query")
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	if m, ok := rv.(map[string]interface{}); ok {
    48  		return m, nil
    49  	}
    50  	return nil, errors.New("query string does not parse to a doc")
    51  }
    52  
    53  type LogLineParser struct {
    54  	Buffer string
    55  	Fields map[string]interface{}
    56  
    57  	runes    []rune
    58  	position int
    59  }
    60  
    61  func (p *LogLineParser) Init() {
    62  	p.runes = append([]rune(p.Buffer), endRune)
    63  	p.Fields = make(map[string]interface{})
    64  }
    65  
    66  func (p *LogLineParser) Parse() error {
    67  	var err error
    68  	if err = p.parseTimestamp(); err != nil {
    69  		return err
    70  	}
    71  	if p.eatWS().lookahead(0) == '[' {
    72  		// we assume version < 3.0
    73  		if err = p.parseContext(); err != nil {
    74  			return err
    75  		}
    76  		err = p.parseMessage()
    77  	} else {
    78  		// we assume version > 3.0
    79  		if err = p.parseSeverity(); err != nil {
    80  			return err
    81  		}
    82  		if err = p.parseComponent(); err != nil {
    83  			return err
    84  		}
    85  		if err = p.parseContext(); err != nil {
    86  			return err
    87  		}
    88  		err = p.parseMessage()
    89  	}
    90  
    91  	if err != nil {
    92  		return partialLogLineError{InnerError: err}
    93  	}
    94  
    95  	return nil
    96  }
    97  
    98  func (p *LogLineParser) parseTimestamp() error {
    99  	var readTimestamp string
   100  	var err error
   101  
   102  	c := p.eatWS().lookahead(0)
   103  	if unicode.IsDigit(c) {
   104  		// we assume it's either iso8601-utc or iso8601-local
   105  		if readTimestamp, err = p.readUntil(unicode.Space); err != nil {
   106  			return err
   107  		}
   108  	} else {
   109  		// we assume it's ctime or ctime-no-ms
   110  		var dayOfWeek, month, day, time string
   111  
   112  		if dayOfWeek, err = validDayOfWeek(p.readUntil(unicode.Space)); err != nil {
   113  			return err
   114  		}
   115  
   116  		if month, err = validMonth(p.eatWS().readUntil(unicode.Space)); err != nil {
   117  			return err
   118  		}
   119  
   120  		if day, err = p.eatWS().readUntil(unicode.Space); err != nil {
   121  			return err
   122  		}
   123  
   124  		if time, err = p.eatWS().readUntil(unicode.Space); err != nil {
   125  			return err
   126  		}
   127  		readTimestamp = dayOfWeek + " " + month + " " + day + " " + time
   128  	}
   129  
   130  	p.Fields["timestamp"] = readTimestamp
   131  	return nil
   132  }
   133  
   134  func (p *LogLineParser) parseSeverity() error {
   135  	var err error
   136  	if p.Fields["severity"], err = severityToString(p.eatWS().advance()); err != nil {
   137  		return err
   138  	}
   139  	if err = p.expect(unicode.Space); err != nil {
   140  		return err
   141  	}
   142  	return nil
   143  }
   144  
   145  func (p *LogLineParser) parseComponent() error {
   146  	var component string
   147  	var err error
   148  
   149  	if p.eatWS().lookahead(0) == '-' {
   150  		component = "-"
   151  		p.advance() // skip the '-'
   152  	} else {
   153  		if component, err = p.readWhile([]interface{}{unicode.Upper}); err != nil {
   154  			return err
   155  		}
   156  	}
   157  	if !p.validComponentName(component) {
   158  		return errors.New("unrecognized component name")
   159  	}
   160  
   161  	p.Fields["component"] = component
   162  	return nil
   163  }
   164  
   165  func (p *LogLineParser) parseContext() error {
   166  	var err error
   167  	if err = p.eatWS().expect('['); err != nil {
   168  		return err
   169  	}
   170  
   171  	var context string
   172  	if context, err = p.readUntilRune(']'); err != nil {
   173  		return err
   174  	}
   175  	p.advance() // skip the ']'
   176  
   177  	p.Fields["context"] = context
   178  	return nil
   179  }
   180  
   181  func (p *LogLineParser) parseSharding() error {
   182  	message, err := p.readUntilRune(':')
   183  	if err != nil {
   184  		return err
   185  	}
   186  
   187  	p.advance() // skip the ':'
   188  	p.eatWS()
   189  
   190  	if !strings.HasPrefix(message, "about to log metadata event into") {
   191  		return errors.New("unrecognized sharding log line")
   192  	}
   193  
   194  	p.Fields["sharding_message"] = message
   195  	lastSpace := strings.LastIndex(message, " ")
   196  	p.Fields["sharding_collection"] = message[lastSpace+1:]
   197  
   198  	var changelog interface{}
   199  	if changelog, err = p.parseJSONMap(); err != nil {
   200  		return err
   201  	}
   202  	p.Fields["sharding_changelog"] = changelog
   203  	return nil
   204  }
   205  
   206  func (p *LogLineParser) parseMessage() error {
   207  	p.eatWS()
   208  
   209  	savedPosition := p.position
   210  
   211  	if p.Fields["component"] == "SHARDING" {
   212  		savedPosition := p.position
   213  		err := p.parseSharding()
   214  		if err == nil {
   215  			return nil
   216  		}
   217  		p.position = savedPosition
   218  	}
   219  
   220  	// check if this message is an operation
   221  	operation, err := p.readUntil(unicode.Space)
   222  	if err == nil && p.validOperationName(operation) {
   223  		// yay, an operation.
   224  		p.Fields["operation"] = operation
   225  
   226  		var namespace string
   227  		if namespace, err = p.eatWS().readUntil(unicode.Space); err != nil {
   228  			return err
   229  		}
   230  		p.Fields["namespace"] = namespace
   231  
   232  		if err = p.parseOperationBody(); err != nil {
   233  			return err
   234  		}
   235  	} else {
   236  		p.position = savedPosition
   237  
   238  		if p.Fields["message"], err = p.readUntilEOL(); err != nil {
   239  			return err
   240  		}
   241  	}
   242  
   243  	return nil
   244  }
   245  
   246  func (p *LogLineParser) parseOperationBody() error {
   247  	for p.runes[p.position] != endRune {
   248  		var err error
   249  		var done bool
   250  
   251  		if done, err = p.parseFieldAndValue(); err != nil {
   252  			return err
   253  		}
   254  		if done {
   255  			// check for a duration
   256  			dur, err := p.readDuration()
   257  			if err != nil {
   258  				return err
   259  			}
   260  			p.Fields["duration_ms"] = dur
   261  			break
   262  		}
   263  	}
   264  	return nil
   265  }
   266  
   267  func (p *LogLineParser) parseFieldAndValue() (bool, error) {
   268  	var fieldName string
   269  	var fieldValue interface{}
   270  	var err error
   271  
   272  	p.eatWS()
   273  
   274  	savedPosition := p.position
   275  	if fieldName, err = p.readWhileNot([]interface{}{':', unicode.Space}); err != nil {
   276  		p.position = savedPosition
   277  		return true, nil // swallow the error to give our caller a change to backtrack
   278  	}
   279  	p.advance() // skip the ':'/WS
   280  	p.eatWS()   // end eat any remaining WS
   281  
   282  	// some known fields have a more complicated structure
   283  	if fieldName == "planSummary" {
   284  		if fieldValue, err = p.parsePlanSummary(); err != nil {
   285  			return false, err
   286  		}
   287  	} else if fieldName == "command" {
   288  		// >=2.6 has:  command: <command_name> <command_doc>?
   289  		// <2.6 has:   command: <command_doc>
   290  		firstCharInVal := p.lookahead(0)
   291  		if firstCharInVal != '{' {
   292  			name, err := p.readJSONIdentifier()
   293  			if err != nil {
   294  				return false, err
   295  			}
   296  			p.eatWS()
   297  			p.Fields["command_type"] = name
   298  		}
   299  
   300  		if fieldValue, err = p.parseJSONMap(); err != nil {
   301  			return false, err
   302  		}
   303  	} else if fieldName == "locks(micros)" {
   304  		// < 2.8
   305  		if fieldValue, err = p.parseLocksMicro(); err != nil {
   306  			return false, err
   307  		}
   308  		p.eatWS()
   309  	} else {
   310  		if fieldValue, err = p.parseFieldValue(fieldName); err != nil {
   311  			return false, err
   312  		}
   313  		if !p.validFieldName(fieldName) {
   314  			return false, nil
   315  		}
   316  	}
   317  	p.Fields[fieldName] = fieldValue
   318  	return false, nil
   319  }
   320  
   321  func (p *LogLineParser) validFieldName(fieldName string) bool {
   322  	if len(fieldName) == 0 {
   323  		return false
   324  	}
   325  	for _, c := range fieldName {
   326  		switch {
   327  		case unicode.IsLetter(c):
   328  			continue
   329  		case unicode.IsDigit(c):
   330  			continue
   331  		case c == '_':
   332  			continue
   333  		case c == '$':
   334  			continue
   335  		default:
   336  			return false
   337  		}
   338  	}
   339  	return true
   340  }
   341  
   342  func (p *LogLineParser) parseFieldValue(fieldName string) (interface{}, error) {
   343  	var fieldValue interface{}
   344  	var err error
   345  
   346  	firstCharInVal := p.lookahead(0)
   347  	switch {
   348  	case firstCharInVal == '{':
   349  		if fieldValue, err = p.parseJSONMap(); err != nil {
   350  			return nil, err
   351  		}
   352  	case unicode.IsDigit(firstCharInVal):
   353  		if fieldValue, err = p.readNumber(); err != nil {
   354  			return nil, err
   355  		}
   356  	case unicode.IsLetter(firstCharInVal):
   357  		if fieldValue, err = p.readJSONIdentifier(); err != nil {
   358  			return nil, err
   359  		}
   360  	case firstCharInVal == '"':
   361  		if fieldValue, err = p.readStringValue(firstCharInVal); err != nil {
   362  			return nil, err
   363  		}
   364  	default:
   365  		return nil, fmt.Errorf("unexpected start character for value of field '%s'", fieldName)
   366  	}
   367  	return fieldValue, nil
   368  }
   369  
   370  func (p *LogLineParser) parseLocksMicro() (map[string]int64, error) {
   371  	rv := make(map[string]int64)
   372  
   373  	for {
   374  		c := p.eatWS().lookahead(0)
   375  		if c != 'r' && c != 'R' && c != 'w' && c != 'W' {
   376  			return rv, nil
   377  		} else if p.lookahead(1) != ':' {
   378  			return rv, nil
   379  		}
   380  
   381  		p.advance()
   382  		p.advance()
   383  
   384  		// not strictly correct - the value here should be an integer, not a float
   385  		var duration float64
   386  		var err error
   387  		if duration, err = p.readNumber(); err != nil {
   388  			return nil, err
   389  		}
   390  		rv[string([]rune{c})] = int64(duration)
   391  	}
   392  
   393  }
   394  
   395  func (p *LogLineParser) parsePlanSummary() (interface{}, error) {
   396  	var rv []interface{}
   397  
   398  	for {
   399  		elem, err := p.parsePlanSummaryElement()
   400  		if err != nil {
   401  			return nil, err
   402  		}
   403  		if elem != nil {
   404  			rv = append(rv, elem)
   405  		}
   406  
   407  		if p.eatWS().lookahead(0) != ',' {
   408  			break
   409  		} else {
   410  			p.advance() // skip the ','
   411  		}
   412  	}
   413  
   414  	return rv, nil
   415  }
   416  
   417  func (p *LogLineParser) parsePlanSummaryElement() (interface{}, error) {
   418  	rv := make(map[string]interface{})
   419  
   420  	p.eatWS()
   421  
   422  	savedPosition := p.position
   423  
   424  	var stage string
   425  	var err error
   426  
   427  	if stage, err = p.readUpcaseIdentifier(); err != nil {
   428  		p.position = savedPosition
   429  		return nil, nil
   430  	}
   431  
   432  	c := p.eatWS().lookahead(0)
   433  	if c == '{' {
   434  		if rv[stage], err = p.parseJSONMap(); err != nil {
   435  			return nil, nil
   436  		}
   437  	} else {
   438  		rv[stage] = true
   439  	}
   440  
   441  	return rv, nil
   442  }
   443  
   444  func (p *LogLineParser) readNumber() (float64, error) {
   445  	startPosition := p.position
   446  	endPosition := startPosition
   447  	numberChecks := []interface{}{unicode.Digit, '.', '+', '-', 'e', 'E'}
   448  	for check(p.runes[endPosition], numberChecks) {
   449  		endPosition++
   450  	}
   451  
   452  	if p.runes[endPosition] == endRune {
   453  		return 0, errors.New("found end of line before expected unicode range")
   454  	}
   455  
   456  	p.position = endPosition
   457  
   458  	return strconv.ParseFloat(string(p.runes[startPosition:endPosition]), 64)
   459  }
   460  
   461  func (p *LogLineParser) readDuration() (float64, error) {
   462  	startPosition := p.position
   463  	endPosition := startPosition
   464  
   465  	for unicode.IsDigit(p.runes[endPosition]) {
   466  		endPosition++
   467  	}
   468  
   469  	if p.runes[endPosition] != 'm' || p.runes[endPosition+1] != 's' {
   470  		return 0, errors.New("invalid duration specifier")
   471  	}
   472  
   473  	rv, err := strconv.ParseFloat(string(p.runes[startPosition:endPosition]), 64)
   474  	p.position = endPosition + 2
   475  	return rv, err
   476  }
   477  
   478  func (p *LogLineParser) parseJSONMap() (interface{}, error) {
   479  	// we assume we're on the '{'
   480  	if err := p.expect('{'); err != nil {
   481  		return nil, err
   482  	}
   483  
   484  	rv := make(map[string]interface{})
   485  
   486  	for {
   487  		var key string
   488  		var value interface{}
   489  		var err error
   490  
   491  		// we support keys both of the form: { foo: ... } and { "foo": ... }
   492  		fc := p.eatWS().lookahead(0)
   493  		if fc == '"' || fc == '\'' {
   494  			if key, err = p.readStringValue(fc); err != nil {
   495  				return nil, err
   496  			}
   497  		} else {
   498  			if key, err = p.readJSONIdentifier(); err != nil {
   499  				return nil, err
   500  			}
   501  		}
   502  
   503  		if key != "" {
   504  			if err = p.eatWS().expect(':'); err != nil {
   505  				return nil, err
   506  			}
   507  			if value, err = p.eatWS().parseJSONValue(); err != nil {
   508  				return nil, err
   509  			}
   510  			rv[key] = value
   511  		}
   512  
   513  		commaOrRbrace := p.eatWS().lookahead(0)
   514  		if commaOrRbrace == '}' {
   515  			p.position++
   516  			break
   517  		} else if commaOrRbrace == ',' {
   518  			p.position++
   519  		} else {
   520  			return nil, errors.New("expected '}' or ',' in json")
   521  		}
   522  
   523  	}
   524  
   525  	return rv, nil
   526  }
   527  
   528  func (p *LogLineParser) parseJSONArray() (interface{}, error) {
   529  	var rv []interface{}
   530  
   531  	// we assume we're on the '['
   532  	if err := p.expect('['); err != nil {
   533  		return nil, err
   534  	}
   535  
   536  	if p.eatWS().lookahead(0) == ']' {
   537  		p.advance()
   538  		return rv, nil
   539  	}
   540  
   541  	for {
   542  		var value interface{}
   543  		var err error
   544  
   545  		if value, err = p.eatWS().parseJSONValue(); err != nil {
   546  			return nil, err
   547  		}
   548  
   549  		rv = append(rv, value)
   550  
   551  		commaOrRbrace := p.eatWS().lookahead(0)
   552  		if commaOrRbrace == ']' {
   553  			p.position++
   554  			break
   555  		} else if commaOrRbrace == ',' {
   556  			p.position++
   557  		} else {
   558  			return nil, errors.New("expected ']' or ',' in json")
   559  		}
   560  	}
   561  
   562  	return rv, nil
   563  }
   564  
   565  func (p *LogLineParser) parseJSONValue() (interface{}, error) {
   566  	var value interface{}
   567  	var err error
   568  
   569  	firstCharInVal := p.lookahead(0)
   570  	switch {
   571  	case firstCharInVal == '{':
   572  		if value, err = p.parseJSONMap(); err != nil {
   573  			return nil, err
   574  		}
   575  	case firstCharInVal == '[':
   576  		if value, err = p.parseJSONArray(); err != nil {
   577  			return nil, err
   578  		}
   579  	case check(firstCharInVal, []interface{}{unicode.Digit, '-', '+', '.'}):
   580  		if value, err = p.readNumber(); err != nil {
   581  			return nil, err
   582  		}
   583  	case firstCharInVal == '"':
   584  		// mongo doesn't follow generally accepted rules on how to handle nested quotes
   585  		// when the inner quote character matches the outer quote character (escaping the inner
   586  		// quote with a \).
   587  
   588  		// so we have to do something equally terrible to read these values.  we look ahead until we
   589  		// find a value separator or an end to a json value - , ] }
   590  		// that occurs after an even number of quotes.
   591  
   592  		savedPosition := p.position + 1
   593  		endPosition := savedPosition
   594  
   595  		quoteCount := 1
   596  		quotePosition := savedPosition - 1
   597  
   598  		if endPosition < len(p.runes)-1 {
   599  			lastRune := '"'
   600  
   601  			for {
   602  				r := p.runes[endPosition]
   603  				if r == '"' {
   604  					quoteCount++
   605  					quotePosition = endPosition
   606  				} else if (r == ',' || r == '}' || r == ']') && lastRune == '"' {
   607  					if quoteCount%2 == 0 {
   608  						value = string(p.runes[savedPosition:quotePosition])
   609  						p.position = quotePosition + 1
   610  						break
   611  					}
   612  				}
   613  
   614  				if !unicode.IsSpace(r) {
   615  					lastRune = r
   616  				}
   617  
   618  				endPosition++
   619  				if endPosition == len(p.runes) {
   620  					return nil, errors.New("unexpected end of line reading json value")
   621  				}
   622  			}
   623  		}
   624  	case unicode.IsLetter(firstCharInVal):
   625  		if value, err = p.readJSONIdentifier(); err != nil {
   626  			return nil, err
   627  		}
   628  		if value == "null" {
   629  			value = nil
   630  		} else if value == "true" {
   631  			value = true
   632  		} else if value == "false" {
   633  			value = false
   634  		} else if value == "new" {
   635  			if value, err = p.eatWS().readJSONIdentifier(); err != nil {
   636  				return nil, err
   637  			}
   638  			if value != "Date" {
   639  				return nil, fmt.Errorf("unexpected constructor: %s", value)
   640  			}
   641  			// we expect "new Date(123456789)"
   642  			if err = p.expect('('); err != nil {
   643  				return nil, err
   644  			}
   645  			var dateNum float64
   646  			if dateNum, err = p.readNumber(); err != nil {
   647  				return nil, err
   648  			}
   649  			if err = p.expect(')'); err != nil {
   650  				return nil, err
   651  			}
   652  
   653  			if math.Floor(dateNum) != dateNum {
   654  				return nil, errors.New("expected int in `new Date()`")
   655  			}
   656  			unixSec := int64(dateNum) / 1000
   657  			unixNS := int64(dateNum) % 1000 * 1000000
   658  			value = time.Unix(unixSec, unixNS)
   659  		} else if value == "Timestamp" {
   660  			var ts string
   661  			if p.lookahead(0) == '(' {
   662  				p.position++
   663  				if ts, err = p.readUntilRune(')'); err != nil {
   664  					return nil, err
   665  				}
   666  				p.position++
   667  			} else {
   668  				if ts, err = p.eatWS().readWhile([]interface{}{unicode.Digit, '|'}); err != nil {
   669  					return nil, err
   670  				}
   671  			}
   672  			value = "Timestamp(" + ts + ")"
   673  		} else if value == "ObjectId" {
   674  			if err = p.expect('('); err != nil {
   675  				return nil, err
   676  			}
   677  			quote := p.lookahead(0) // keep ahold of the quote so we can match it
   678  			if p.lookahead(0) != '\'' && p.lookahead(0) != '"' {
   679  				return nil, errors.New("expected ' or \" in ObjectId")
   680  			}
   681  			p.position++
   682  
   683  			hexRunes := []interface{}{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f'}
   684  			var hex string
   685  			if hex, err = p.eatWS().readWhile(hexRunes); err != nil {
   686  				return nil, err
   687  			}
   688  			if err = p.expect(quote); err != nil {
   689  				return nil, err
   690  			}
   691  			if err = p.expect(')'); err != nil {
   692  				return nil, err
   693  			}
   694  			value = "ObjectId(" + hex + ")"
   695  		} else if value == "BinData" {
   696  			// BinData looks something like this:
   697  			// BinData(0, D296E984640196C4D2977BECF468865948F7704C)
   698  			// It's not very interesting, but we need to handle it
   699  			if err = p.expect('('); err != nil {
   700  				return nil, err
   701  			}
   702  
   703  			var bindata string
   704  			if bindata, err = p.readUntilRune(')'); err != nil {
   705  				return nil, err
   706  			}
   707  
   708  			if err = p.expect(')'); err != nil {
   709  				return nil, err
   710  			}
   711  
   712  			value = "BinData(" + bindata + ")"
   713  		} else if value == "UUID" {
   714  			// UUID looks something like this:
   715  			// UUID("bac26ad1-3d76-4da1-94cc-d541942f6889")
   716  			// It's not very interesting, but we need to handle it
   717  			if err = p.expect('('); err != nil {
   718  				return nil, err
   719  			}
   720  
   721  			var uuid string
   722  			if uuid, err = p.readUntilRune(')'); err != nil {
   723  				return nil, err
   724  			}
   725  
   726  			if err = p.expect(')'); err != nil {
   727  				return nil, err
   728  			}
   729  
   730  			value = "UUID(" + uuid + ")"
   731  		} else {
   732  			return nil, fmt.Errorf("unexpected start of JSON value: %s", value)
   733  		}
   734  	case firstCharInVal == '/':
   735  		// Case for regex query field value
   736  		// for example: { field: /^numbersOrLetters.?.?$/ }
   737  		var exp string
   738  		if err := p.expect('/'); err != nil {
   739  			return nil, err
   740  		}
   741  		if exp, err = p.readUntilRune('/'); err != nil {
   742  			return nil, err
   743  		}
   744  
   745  		var moreExp string
   746  		var expression bytes.Buffer
   747  		expression.WriteString(exp)
   748  		// The '/' can be escaped, so keep looking for unescaped end of expression
   749  		for {
   750  			if p.lookahead(-1) != '\\' {
   751  				break
   752  			}
   753  			p.position++
   754  			if moreExp, err = p.readUntilRune('/'); err != nil {
   755  				return nil, err
   756  			}
   757  			expression.WriteString("/")
   758  			expression.WriteString(moreExp)
   759  		}
   760  		if err = p.expect('/'); err != nil {
   761  			return nil, err
   762  		}
   763  		value = "/" + expression.String() + "/"
   764  	default:
   765  		return nil, fmt.Errorf("unexpected start character for JSON value of field: %c", firstCharInVal)
   766  	}
   767  
   768  	return value, nil
   769  }
   770  
   771  func (p *LogLineParser) readStringValue(quote rune) (string, error) {
   772  	var s string
   773  	var err error
   774  
   775  	p.advance() // skip starting quote
   776  	if s, err = p.readUntilRune(quote); err != nil {
   777  		return "", err
   778  	}
   779  	p.advance() // skip ending quote
   780  
   781  	return s, nil
   782  }
   783  
   784  func (p *LogLineParser) readJSONIdentifier() (string, error) {
   785  	startPosition := p.position
   786  	endPosition := startPosition
   787  
   788  	for check(p.runes[endPosition], []interface{}{unicode.Letter, unicode.Digit, '$', '_', '.', '*'}) {
   789  		endPosition++
   790  	}
   791  
   792  	p.position = endPosition
   793  	return string(p.runes[startPosition:endPosition]), nil
   794  }
   795  
   796  func (p *LogLineParser) readUpcaseIdentifier() (string, error) {
   797  	return p.readWhile([]interface{}{unicode.Upper, unicode.Digit, '_'})
   798  }
   799  
   800  func (p *LogLineParser) readAlphaIdentifier() (string, error) {
   801  	return p.readWhile([]interface{}{unicode.Letter, unicode.Digit, '_'})
   802  }
   803  
   804  func (p *LogLineParser) readUntil(untilRangeTable *unicode.RangeTable) (string, error) {
   805  	startPosition := p.position
   806  	endPosition := startPosition
   807  	for p.runes[endPosition] != endRune && !unicode.Is(untilRangeTable, p.runes[endPosition]) {
   808  		endPosition++
   809  	}
   810  
   811  	if p.runes[endPosition] == endRune {
   812  		return "", errors.New("found end of line before expected unicode range")
   813  	}
   814  
   815  	p.position = endPosition
   816  
   817  	return string(p.runes[startPosition:endPosition]), nil
   818  }
   819  
   820  func (p *LogLineParser) readUntilRune(untilRune rune) (string, error) {
   821  	startPosition := p.position
   822  	endPosition := startPosition
   823  	for p.runes[endPosition] != untilRune && p.runes[endPosition] != endRune {
   824  		endPosition++
   825  	}
   826  
   827  	if p.runes[endPosition] == endRune && untilRune != endRune {
   828  		return "", fmt.Errorf("found end of line before expected rune '%c'", untilRune)
   829  	}
   830  
   831  	p.position = endPosition
   832  
   833  	return string(p.runes[startPosition:endPosition]), nil
   834  }
   835  
   836  func (p *LogLineParser) readUntilEOL() (string, error) {
   837  	return p.readUntilRune(endRune)
   838  }
   839  
   840  func (p *LogLineParser) readWhile(checks []interface{}) (string, error) {
   841  	return p._readWhile(checks, false)
   842  }
   843  
   844  func (p *LogLineParser) readWhileNot(checks []interface{}) (string, error) {
   845  	return p._readWhile(checks, true)
   846  }
   847  
   848  func (p *LogLineParser) _readWhile(checks []interface{}, checkStopVal bool) (string, error) {
   849  	startPosition := p.position
   850  	endPosition := startPosition
   851  
   852  	for p.runes[endPosition] != endRune {
   853  		if check(p.runes[endPosition], checks) == checkStopVal {
   854  			break
   855  		}
   856  		endPosition++
   857  	}
   858  
   859  	if p.runes[endPosition] == endRune {
   860  		return "", errors.New("unexpected end of line")
   861  	}
   862  
   863  	p.position = endPosition
   864  
   865  	return string(p.runes[startPosition:endPosition]), nil
   866  }
   867  
   868  func (p *LogLineParser) lookahead(amount int) rune {
   869  	return p.runes[p.position+amount]
   870  }
   871  
   872  func (p *LogLineParser) matchAhead(startIdx int, s string) bool {
   873  	runes := []rune(s)
   874  	for i, r := range runes {
   875  		if r != p.runes[startIdx+i] {
   876  			return false
   877  		}
   878  	}
   879  	return true
   880  }
   881  
   882  func (p *LogLineParser) advance() rune {
   883  	r := p.runes[p.position]
   884  	p.position++
   885  	return r
   886  }
   887  
   888  func (p *LogLineParser) expect(c interface{}) error {
   889  	r := p.advance()
   890  	matches := doCheck(r, c)
   891  	if !matches {
   892  		return fmt.Errorf("unexpected '%c'", r)
   893  	}
   894  	return nil
   895  }
   896  
   897  func (p *LogLineParser) eatWS() *LogLineParser {
   898  	for unicode.Is(unicode.Space, p.runes[p.position]) {
   899  		p.position++
   900  	}
   901  	return p
   902  }
   903  
   904  func severityToString(sev rune) (string, error) {
   905  	switch sev {
   906  	case 'D':
   907  		return "debug", nil
   908  	case 'I':
   909  		return "informational", nil
   910  	case 'W':
   911  		return "warning", nil
   912  	case 'E':
   913  		return "error", nil
   914  	case 'F':
   915  		return "fatal", nil
   916  	default:
   917  		return "", fmt.Errorf("unknown severity '%c'", sev)
   918  	}
   919  }
   920  
   921  func check(r rune, checks []interface{}) bool {
   922  	for _, c := range checks {
   923  		if doCheck(r, c) {
   924  			return true
   925  		}
   926  	}
   927  	return false
   928  }
   929  
   930  func doCheck(r rune, c interface{}) bool {
   931  	if rt, ok := c.(*unicode.RangeTable); ok {
   932  		if unicode.Is(rt, r) {
   933  			return true
   934  		}
   935  	} else if runeCheck, ok := c.(rune); ok {
   936  		if r == runeCheck {
   937  			return true
   938  		}
   939  	} else {
   940  		panic("unhandled check in doCheck")
   941  	}
   942  	return false
   943  }
   944  func validDayOfWeek(dayOfWeek string, err error) (string, error) {
   945  	if len(dayOfWeek) != 3 {
   946  		return "", errors.New("invalid day of week")
   947  	}
   948  	// XXX(toshok) validate against a list?
   949  	return dayOfWeek, nil
   950  }
   951  
   952  func validMonth(month string, err error) (string, error) {
   953  	if len(month) != 3 {
   954  		return "", errors.New("invalid month")
   955  	}
   956  	// XXX(toshok) validate against a list?
   957  	return month, nil
   958  }
   959  
   960  func (p *LogLineParser) validOperationName(s string) bool {
   961  	return s == "query" ||
   962  		s == "getmore" ||
   963  		s == "insert" ||
   964  		s == "update" ||
   965  		s == "remove" ||
   966  		s == "command" ||
   967  		s == "killcursors"
   968  }
   969  
   970  func (p *LogLineParser) validComponentName(s string) bool {
   971  	return s == "ACCESS" ||
   972  		s == "COMMAND" ||
   973  		s == "CONTROL" ||
   974  		s == "GEO" ||
   975  		s == "INDEX" ||
   976  		s == "NETWORK" ||
   977  		s == "QUERY" ||
   978  		s == "REPL" ||
   979  		s == "SHARDING" ||
   980  		s == "STORAGE" ||
   981  		s == "JOURNAL" ||
   982  		s == "WRITE" ||
   983  		s == "TOTAL" ||
   984  		s == "-"
   985  }