-
Notifications
You must be signed in to change notification settings - Fork 88
/
Copy pathparser.go
105 lines (96 loc) · 3.01 KB
/
parser.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package gonx
import (
"bufio"
"fmt"
"io"
"regexp"
"strings"
)
// StringParser is the interface that wraps the ParseString method.
type StringParser interface {
ParseString(line string) (entry *Entry, err error)
}
// Parser is a log record parser. Use specific constructors to initialize it.
type Parser struct {
Format string
Regexp *regexp.Regexp
}
// NewParser returns a new Parser, use given log format to create its internal
// strings parsing regexp.
func NewParser(format string) *Parser {
// First split up multiple concatenated fields with placeholder
placeholder := " _PLACEHOLDER___ "
preparedFormat := format
concatenatedRe := regexp.MustCompile(`[A-Za-z0-9_]\$[A-Za-z0-9_]`)
for concatenatedRe.MatchString(preparedFormat) {
preparedFormat = regexp.MustCompile(`([A-Za-z0-9_])\$([A-Za-z0-9_]+)(\\?([^$\\A-Za-z0-9_]))`).ReplaceAllString(
preparedFormat, fmt.Sprintf("${1}${3}%s$$${2}${3}", placeholder),
)
}
// Second replace each fileds to regexp grouping
quotedFormat := regexp.QuoteMeta(preparedFormat + " ")
re := regexp.MustCompile(`\\\$([A-Za-z0-9_]+)(?:\\\$[A-Za-z0-9_])*(\\?([^$\\A-Za-z0-9_]))`).ReplaceAllString(
quotedFormat, "(?P<$1>[^$3]*)$2")
// Finally remove placeholder
re = regexp.MustCompile(fmt.Sprintf(".%s", placeholder)).ReplaceAllString(re, "")
return &Parser{format, regexp.MustCompile(fmt.Sprintf("^%v", strings.Trim(re, " ")))}
}
// ParseString parses a log file line using internal format regexp. If a line
// does not match the given format an error will be returned.
func (parser *Parser) ParseString(line string) (entry *Entry, err error) {
re := parser.Regexp
fields := re.FindStringSubmatch(line)
if fields == nil {
err = fmt.Errorf("access log line '%v' does not match given format '%v'", line, re)
return
}
// Iterate over subexp foung and fill the map record
entry = NewEmptyEntry()
for i, name := range re.SubexpNames() {
if i == 0 {
continue
}
entry.SetField(name, fields[i])
}
return
}
// NewNginxParser parses the nginx conf file to find log_format with the given
// name and returns a parser for this format. It returns an error if cannot find
// the given log format.
func NewNginxParser(conf io.Reader, name string) (parser *Parser, err error) {
scanner := bufio.NewScanner(conf)
re := regexp.MustCompile(fmt.Sprintf(`^\s*log_format\s+%v\s+(.+)\s*$`, name))
found := false
var format string
for scanner.Scan() {
var line string
if !found {
// Find a log_format definition
line = scanner.Text()
formatDef := re.FindStringSubmatch(line)
if formatDef == nil {
continue
}
found = true
line = formatDef[1]
} else {
line = scanner.Text()
}
// Look for a definition end
re = regexp.MustCompile(`^\s*(.*?)\s*(;|$)`)
lineSplit := re.FindStringSubmatch(line)
if l := len(lineSplit[1]); l > 2 {
format += lineSplit[1][1 : l-1]
}
if lineSplit[2] == ";" {
break
}
}
if !found {
err = fmt.Errorf("`log_format %v` not found in given config", name)
} else {
err = scanner.Err()
}
parser = NewParser(format)
return
}