Files
topip/topip.go
tchivert 4976d0e62c
build / build (push) Successful in 1m15s
add pattern search
2024-12-29 14:16:14 +01:00

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])
}