133 lines
3.3 KiB
Go
133 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
st "git.ipng.ch/ipng/nginx-logtail/internal/store"
|
|
pb "git.ipng.ch/ipng/nginx-logtail/proto/logtailpb"
|
|
)
|
|
|
|
// sharedFlags holds the flags common to every subcommand.
|
|
type sharedFlags struct {
|
|
targets []string
|
|
jsonOut bool
|
|
website string
|
|
prefix string
|
|
uri string
|
|
status string // expression: "200", "!=200", ">=400", etc.
|
|
websiteRe string // RE2 regex against website
|
|
uriRe string // RE2 regex against request URI
|
|
}
|
|
|
|
// bindShared registers the shared flags on fs and returns a pointer to the
|
|
// populated struct. Call fs.Parse before reading the struct.
|
|
func bindShared(fs *flag.FlagSet) (*sharedFlags, *string) {
|
|
sf := &sharedFlags{}
|
|
target := fs.String("target", "localhost:9090", "comma-separated host:port list")
|
|
fs.BoolVar(&sf.jsonOut, "json", false, "emit newline-delimited JSON")
|
|
fs.StringVar(&sf.website, "website", "", "filter: exact website match")
|
|
fs.StringVar(&sf.prefix, "prefix", "", "filter: exact client prefix match")
|
|
fs.StringVar(&sf.uri, "uri", "", "filter: exact request URI match")
|
|
fs.StringVar(&sf.status, "status", "", "filter: HTTP status expression (200, !=200, >=400, <500, …)")
|
|
fs.StringVar(&sf.websiteRe, "website-re", "", "filter: RE2 regex against website")
|
|
fs.StringVar(&sf.uriRe, "uri-re", "", "filter: RE2 regex against request URI")
|
|
return sf, target
|
|
}
|
|
|
|
func (sf *sharedFlags) resolve(target string) {
|
|
sf.targets = parseTargets(target)
|
|
}
|
|
|
|
func parseTargets(s string) []string {
|
|
seen := make(map[string]bool)
|
|
var out []string
|
|
for _, t := range strings.Split(s, ",") {
|
|
t = strings.TrimSpace(t)
|
|
if t == "" || seen[t] {
|
|
continue
|
|
}
|
|
seen[t] = true
|
|
out = append(out, t)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func buildFilter(sf *sharedFlags) *pb.Filter {
|
|
if sf.website == "" && sf.prefix == "" && sf.uri == "" && sf.status == "" && sf.websiteRe == "" && sf.uriRe == "" {
|
|
return nil
|
|
}
|
|
f := &pb.Filter{}
|
|
if sf.website != "" {
|
|
f.Website = &sf.website
|
|
}
|
|
if sf.prefix != "" {
|
|
f.ClientPrefix = &sf.prefix
|
|
}
|
|
if sf.uri != "" {
|
|
f.HttpRequestUri = &sf.uri
|
|
}
|
|
if sf.status != "" {
|
|
n, op, ok := st.ParseStatusExpr(sf.status)
|
|
if !ok {
|
|
fmt.Fprintf(os.Stderr, "--status: invalid expression %q; use e.g. 200, !=200, >=400, <500\n", sf.status)
|
|
os.Exit(1)
|
|
}
|
|
f.HttpResponse = &n
|
|
f.StatusOp = op
|
|
}
|
|
if sf.websiteRe != "" {
|
|
f.WebsiteRegex = &sf.websiteRe
|
|
}
|
|
if sf.uriRe != "" {
|
|
f.UriRegex = &sf.uriRe
|
|
}
|
|
return f
|
|
}
|
|
|
|
func parseWindow(s string) pb.Window {
|
|
switch s {
|
|
case "1m":
|
|
return pb.Window_W1M
|
|
case "5m":
|
|
return pb.Window_W5M
|
|
case "15m":
|
|
return pb.Window_W15M
|
|
case "60m":
|
|
return pb.Window_W60M
|
|
case "6h":
|
|
return pb.Window_W6H
|
|
case "24h":
|
|
return pb.Window_W24H
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "--window: unknown value %q; valid: 1m 5m 15m 60m 6h 24h\n", s)
|
|
os.Exit(1)
|
|
panic("unreachable")
|
|
}
|
|
}
|
|
|
|
func parseGroupBy(s string) pb.GroupBy {
|
|
switch s {
|
|
case "website":
|
|
return pb.GroupBy_WEBSITE
|
|
case "prefix":
|
|
return pb.GroupBy_CLIENT_PREFIX
|
|
case "uri":
|
|
return pb.GroupBy_REQUEST_URI
|
|
case "status":
|
|
return pb.GroupBy_HTTP_RESPONSE
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "--group-by: unknown value %q; valid: website prefix uri status\n", s)
|
|
os.Exit(1)
|
|
panic("unreachable")
|
|
}
|
|
}
|
|
|
|
func dieUsage(fs *flag.FlagSet, msg string) {
|
|
fmt.Fprintln(os.Stderr, msg)
|
|
fs.PrintDefaults()
|
|
os.Exit(1)
|
|
}
|