176 lines
4.2 KiB
Go
176 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"text/tabwriter"
|
|
)
|
|
|
|
var (
|
|
file = flag.String("f", "haproxy.log", "Path to the HAProxy log file (use - for stdin)")
|
|
top = flag.String("t", "conn", "Sort by connections or bytes sent (conn or bytes)")
|
|
num = flag.Int("n", 20, "Number of IP addresses and vhosts to display")
|
|
pattern = flag.String("p", "", "Filter by request pattern")
|
|
)
|
|
|
|
type IPData struct {
|
|
IP string
|
|
Count int
|
|
Bytes int64
|
|
Request string
|
|
UserAgent string
|
|
}
|
|
|
|
type VhostData struct {
|
|
Vhost string
|
|
Count int
|
|
Bytes int64
|
|
Request string
|
|
}
|
|
|
|
func usage() {
|
|
fmt.Fprintf(os.Stderr, "Usage: %s [-f logfile] [-t conn|bytes] [-n num] [-p pattern]\n", os.Args[0])
|
|
flag.PrintDefaults()
|
|
fmt.Fprintf(os.Stderr, "\nRecommended haproxy log-format:\n")
|
|
fmt.Fprintf(os.Stderr, " log-format \"%%ci %%b/%%s %%ST %%B %%Tt %%sq/%%bq %%{+Q}r %%hr %%hs\"\n")
|
|
}
|
|
|
|
func main() {
|
|
flag.Usage = usage
|
|
flag.Parse()
|
|
if *file == "-" {
|
|
*file = "/dev/stdin"
|
|
}
|
|
f, err := os.Open(*file)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
defer f.Close()
|
|
|
|
ipData := make(map[string]*IPData)
|
|
vhostData := make(map[string]*VhostData)
|
|
|
|
scanner := bufio.NewScanner(f)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if *pattern != "" && !strings.Contains(line, *pattern) {
|
|
continue
|
|
}
|
|
fields := strings.Fields(line)
|
|
if len(fields) >= 7 {
|
|
ip := fields[3]
|
|
vhost := ""
|
|
userAgent := ""
|
|
if headers := strings.Split(line, "{"); len(headers) > 1 {
|
|
parts := strings.Split(headers[1], "|")
|
|
if len(parts) > 0 {
|
|
vhost = parts[0]
|
|
}
|
|
if len(parts) > 2 {
|
|
userAgent = strings.Split(parts[2], " ")[0]
|
|
userAgent = strings.Trim(userAgent, "}")
|
|
}
|
|
}
|
|
bytes, err := strconv.ParseInt(fields[6], 10, 64)
|
|
request := ""
|
|
if parts := strings.Split(line, "\""); len(parts) > 1 {
|
|
request = parts[1]
|
|
}
|
|
if err == nil {
|
|
if data, ok := ipData[ip]; ok {
|
|
data.Count++
|
|
data.Bytes += bytes
|
|
data.Request = request
|
|
data.UserAgent = userAgent
|
|
} else {
|
|
ipData[ip] = &IPData{IP: ip, Count: 1, Bytes: bytes, Request: request, UserAgent: userAgent}
|
|
}
|
|
if data, ok := vhostData[vhost]; ok {
|
|
data.Count++
|
|
data.Bytes += bytes
|
|
data.Request = request
|
|
} else if vhost != "" {
|
|
vhostData[vhost] = &VhostData{Vhost: vhost, Count: 1, Bytes: bytes, Request: request}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
|
|
ipList := make([]*IPData, 0, len(ipData))
|
|
for _, data := range ipData {
|
|
ipList = append(ipList, data)
|
|
}
|
|
vhostList := make([]*VhostData, 0, len(vhostData))
|
|
for _, data := range vhostData {
|
|
vhostList = append(vhostList, data)
|
|
}
|
|
|
|
sort.Slice(ipList, func(i, j int) bool {
|
|
switch *top {
|
|
case "bytes":
|
|
return ipList[i].Bytes > ipList[j].Bytes
|
|
default:
|
|
return ipList[i].Count > ipList[j].Count
|
|
}
|
|
})
|
|
sort.Slice(vhostList, func(i, j int) bool {
|
|
switch *top {
|
|
case "bytes":
|
|
return vhostList[i].Bytes > vhostList[j].Bytes
|
|
default:
|
|
return vhostList[i].Count > vhostList[j].Count
|
|
}
|
|
})
|
|
|
|
ipDisplay := *num
|
|
vhostDisplay := *num
|
|
if ipDisplay > len(ipList) {
|
|
ipDisplay = len(ipList)
|
|
}
|
|
if vhostDisplay > len(vhostList) {
|
|
vhostDisplay = len(vhostList)
|
|
}
|
|
|
|
w := new(tabwriter.Writer)
|
|
w.Init(os.Stdout, 0, 8, 2, '\t', 0)
|
|
fmt.Fprintln(w, "IP\tCount\tBytes\tUser Agent\tLast Request")
|
|
fmt.Fprintln(w, "----\t------\t-----\t-----------\t-------------")
|
|
for i := 0; i < ipDisplay; i++ {
|
|
data := ipList[i]
|
|
fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\n", data.IP, data.Count, formatBytes(data.Bytes), data.UserAgent, data.Request)
|
|
}
|
|
|
|
fmt.Fprintln(w, "")
|
|
|
|
fmt.Fprintln(w, "Vhost\tCount\tBytes\tLast Request")
|
|
fmt.Fprintln(w, "------\t------\t-----\t-------------")
|
|
for i := 0; i < vhostDisplay; i++ {
|
|
data := vhostList[i]
|
|
fmt.Fprintf(w, "%s\t%d\t%s\t%s\n", data.Vhost, data.Count, formatBytes(data.Bytes), data.Request)
|
|
}
|
|
w.Flush()
|
|
}
|
|
|
|
func formatBytes(bytes int64) string {
|
|
const unit = 1024
|
|
if bytes < unit {
|
|
return fmt.Sprintf("%d B", bytes)
|
|
}
|
|
div, exp := int64(unit), 0
|
|
for n := bytes / unit; n >= unit; n /= unit {
|
|
div *= unit
|
|
exp++
|
|
}
|
|
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
|
|
}
|