70 lines
1.6 KiB
Go
70 lines
1.6 KiB
Go
package parser
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
// AccessEntry holds a parsed nginx combined-format access log entry.
|
|
type AccessEntry struct {
|
|
RemoteAddr string
|
|
RemoteUser string
|
|
Time time.Time
|
|
Method string
|
|
Path string
|
|
Protocol string
|
|
Status int
|
|
BytesSent int64
|
|
Referer string
|
|
UserAgent string
|
|
}
|
|
|
|
// accessLogRe matches nginx combined log format.
|
|
// Groups: remoteAddr, remoteUser, time, method, path, protocol, status, bytes, referer, userAgent
|
|
var accessLogRe = regexp.MustCompile(
|
|
`^(\S+) - (\S+) \[([^\]]+)\] "(\S+) (\S+) (\S+)" (\d+) (\d+|-) "([^"]*)" "([^"]*)"$`,
|
|
)
|
|
|
|
const accessTimeLayout = "02/Jan/2006:15:04:05 -0700"
|
|
|
|
// ParseAccessLine parses a single nginx access log line.
|
|
// Returns an error for malformed lines.
|
|
func ParseAccessLine(line string) (AccessEntry, error) {
|
|
m := accessLogRe.FindStringSubmatch(line)
|
|
if m == nil {
|
|
return AccessEntry{}, fmt.Errorf("access: no match: %q", line)
|
|
}
|
|
|
|
t, err := time.Parse(accessTimeLayout, m[3])
|
|
if err != nil {
|
|
return AccessEntry{}, fmt.Errorf("access: parse time %q: %w", m[3], err)
|
|
}
|
|
|
|
status, err := strconv.Atoi(m[7])
|
|
if err != nil {
|
|
return AccessEntry{}, fmt.Errorf("access: parse status %q: %w", m[7], err)
|
|
}
|
|
|
|
var bytes int64
|
|
if m[8] != "-" {
|
|
bytes, err = strconv.ParseInt(m[8], 10, 64)
|
|
if err != nil {
|
|
return AccessEntry{}, fmt.Errorf("access: parse bytes %q: %w", m[8], err)
|
|
}
|
|
}
|
|
|
|
return AccessEntry{
|
|
RemoteAddr: m[1],
|
|
RemoteUser: m[2],
|
|
Time: t,
|
|
Method: m[4],
|
|
Path: m[5],
|
|
Protocol: m[6],
|
|
Status: status,
|
|
BytesSent: bytes,
|
|
Referer: m[9],
|
|
UserAgent: m[10],
|
|
}, nil
|
|
}
|