package main import ( "bufio" "flag" "fmt" "io" "os" "strings" "time" "atlas9.dev/c/core/tracelog" ) func cmdTraces(args []string) { fs := flag.NewFlagSet("atlas9 traces", flag.ExitOnError) idFilter := fs.String("r", "", "only show traces whose request ID contains this string") errsOnly := fs.Bool("errors", false, "only show traces containing WARN or ERROR lines") anomOnly := fs.Bool("anomalies", false, "only show traces flagged by anomaly detection") follow := fs.Bool("f", false, "render traces live as requests complete, e.g. tail -f server.log | atlas9 traces -f") fs.Parse(args) col := &tracelog.Collector{ Out: os.Stdout, Color: tracelog.IsTerminal(os.Stdout), Filter: func(id string, entries []tracelog.Entry) bool { if *idFilter != "" && !strings.Contains(id, *idFilter) { return false } if *anomOnly && len(tracelog.Anomalies(entries)) == 0 { return false } return !*errsOnly || hasProblems(entries) }, } if *follow { sweeper := time.NewTicker(time.Second) defer sweeper.Stop() go func() { for range sweeper.C { col.Sweep(time.Now()) } }() } else { col.MaxAge = 100 * 365 * 24 * time.Hour } scanner := bufio.NewScanner(tracesInput(fs.Args())) scanner.Buffer(make([]byte, 0, 1024*1024), 1024*1024) for scanner.Scan() { id, entry, ok := tracelog.ParseLine(scanner.Bytes()) if !ok || id == "" { continue } col.Add(id, entry) } col.Flush() if err := scanner.Err(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } func tracesInput(files []string) io.Reader { if len(files) == 0 { return os.Stdin } var readers []io.Reader for _, path := range files { f, err := os.Open(path) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } readers = append(readers, f) } return io.MultiReader(readers...) } func hasProblems(entries []tracelog.Entry) bool { for _, e := range entries { if e.Level == "WARN" || e.Level == "ERROR" { return true } } return false }