-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathapache.go
115 lines (107 loc) · 2.55 KB
/
apache.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
106
107
108
109
110
111
112
113
114
115
package axslogparser
import (
"bytes"
"fmt"
"regexp"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
)
// Apache log parser
type Apache struct {
// If set to true, ignores non-fatal errors while parsing line,
// which may leave some fields empty or invalid.
Loose bool
}
var logRe = regexp.MustCompile(
`^(?:(\S+(?:,\s\S+)*)\s)?` + // %v(The canonical ServerName/virtual host) - 192.168.0.1 or 192.168.0.1,192.168.0.2, 192.168.0.3
`(\S+)\s` + // %h(Remote Hostname) $remote_addr
`(\S+)\s` + // %l(Remote Logname)
`([\S\s]+)\s` + // $remote_user
`\[(\d{2}/\w{3}/\d{2}(?:\d{2}:){3}\d{2} [-+]\d{4})\]\s` + // $time_local
`(.*)`)
// Parse for Parser interface
func (ap *Apache) Parse(line string) (*Log, error) {
matches := logRe.FindStringSubmatch(line)
if len(matches) < 1 {
return nil, fmt.Errorf("failed to parse apachelog (not matched): %s", line)
}
l := &Log{
VirtualHost: matches[1],
Host: matches[2],
RemoteLogname: matches[3],
User: matches[4],
}
if l.Host == "-" && l.VirtualHost != "" {
l.Host = l.VirtualHost
l.VirtualHost = ""
l.User = fmt.Sprintf("%s %s", l.RemoteLogname, l.User)
l.RemoteLogname = "-"
}
l.Time, _ = time.Parse(clfTimeLayout, matches[5])
var rest string
l.Request, rest = takeQuoted(matches[6])
if err := l.breakdownRequest(); !ap.Loose && err != nil {
return nil, errors.Wrapf(err, "failed to parse apachelog (invalid request): %s", line)
}
matches = strings.Fields(rest)
if len(matches) < 2 {
return nil, fmt.Errorf("failed to parse apachelog (invalid status or size): %s", line)
}
l.Status, _ = strconv.Atoi(matches[0])
if !ap.Loose {
if l.Status < 100 || 600 <= l.Status {
return nil, fmt.Errorf("failed to parse apachelog (invalid status: %s): %s", matches[0], line)
}
}
l.Size, _ = strconv.ParseUint(matches[1], 10, 64)
l.Referer, rest = takeQuoted(rest)
l.UserAgent, _ = takeQuoted(rest)
return l, nil
}
func takeQuoted(line string) (string, string) {
if line == "" {
return "", ""
}
i := 0
for ; i < len(line); i++ {
if line[i] == '"' {
i++
break
}
}
if i == len(line) {
return "", ""
}
buf := &bytes.Buffer{}
escaped := false
for ; i < len(line); i++ {
c := line[i]
if !escaped {
if c == '"' {
break
}
if c == '\\' {
escaped = true
continue
}
buf.WriteByte(c)
continue
}
escaped = false
switch c {
case 'n':
buf.WriteByte('\n')
case 't':
buf.WriteByte('\t')
case '\\':
buf.WriteByte('\\')
case '"':
buf.WriteByte('"')
default:
buf.WriteByte(c)
}
}
return buf.String(), line[i+1:]
}