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 }