1// Copyright 2015 Marc-Antoine Ruel. All rights reserved. 2// Use of this source code is governed under the Apache License, Version 2.0 3// that can be found in the LICENSE file. 4 5// Package internal implements panicparse 6// 7// It is mostly useful on servers will large number of identical goroutines, 8// making the crash dump harder to read than strictly necessary. 9// 10// Colors: 11// - Magenta: first goroutine to be listed. 12// - Yellow: main package. 13// - Green: standard library. 14// - Red: other packages. 15// 16// Bright colors are used for exported symbols. 17package internal 18 19import ( 20 "errors" 21 "flag" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "log" 26 "os" 27 "os/signal" 28 "regexp" 29 "syscall" 30 31 "github.com/maruel/panicparse/stack" 32 "github.com/mattn/go-colorable" 33 "github.com/mattn/go-isatty" 34 "github.com/mgutz/ansi" 35) 36 37// resetFG is similar to ansi.Reset except that it doesn't reset the 38// background color, only the foreground color and the style. 39// 40// That much for the "ansi" abstraction layer... 41const resetFG = ansi.DefaultFG + "\033[m" 42 43// defaultPalette is the default recommended palette. 44var defaultPalette = Palette{ 45 EOLReset: resetFG, 46 RoutineFirst: ansi.ColorCode("magenta+b"), 47 CreatedBy: ansi.LightBlack, 48 Package: ansi.ColorCode("default+b"), 49 SrcFile: resetFG, 50 FuncStdLib: ansi.Green, 51 FuncStdLibExported: ansi.ColorCode("green+b"), 52 FuncMain: ansi.ColorCode("yellow+b"), 53 FuncOther: ansi.Red, 54 FuncOtherExported: ansi.ColorCode("red+b"), 55 Arguments: resetFG, 56} 57 58func writeToConsole(out io.Writer, p *Palette, buckets []*stack.Bucket, fullPath, needsEnv bool, filter, match *regexp.Regexp) error { 59 if needsEnv { 60 _, _ = io.WriteString(out, "\nTo see all goroutines, visit https://github.com/maruel/panicparse#gotraceback\n\n") 61 } 62 srcLen, pkgLen := CalcLengths(buckets, fullPath) 63 for _, bucket := range buckets { 64 header := p.BucketHeader(bucket, fullPath, len(buckets) > 1) 65 if filter != nil && filter.MatchString(header) { 66 continue 67 } 68 if match != nil && !match.MatchString(header) { 69 continue 70 } 71 _, _ = io.WriteString(out, header) 72 _, _ = io.WriteString(out, p.StackLines(&bucket.Signature, srcLen, pkgLen, fullPath)) 73 } 74 return nil 75} 76 77// process copies stdin to stdout and processes any "panic: " line found. 78// 79// If html is used, a stack trace is written to this file instead. 80func process(in io.Reader, out io.Writer, p *Palette, s stack.Similarity, fullPath, parse, rebase bool, html string, filter, match *regexp.Regexp) error { 81 c, err := stack.ParseDump(in, out, rebase) 82 if c == nil || err != nil { 83 return err 84 } 85 if rebase { 86 log.Printf("GOROOT=%s", c.GOROOT) 87 log.Printf("GOPATH=%s", c.GOPATHs) 88 } 89 needsEnv := len(c.Goroutines) == 1 && showBanner() 90 if parse { 91 stack.Augment(c.Goroutines) 92 } 93 buckets := stack.Aggregate(c.Goroutines, s) 94 if html == "" { 95 return writeToConsole(out, p, buckets, fullPath, needsEnv, filter, match) 96 } 97 return writeToHTML(html, buckets, needsEnv) 98} 99 100func showBanner() bool { 101 if !showGOTRACEBACKBanner { 102 return false 103 } 104 gtb := os.Getenv("GOTRACEBACK") 105 return gtb == "" || gtb == "single" 106} 107 108// Main is implemented here so both 'pp' and 'panicparse' executables can be 109// compiled. This is to work around the Perl Package manager 'pp' that is 110// preinstalled on some OSes. 111func Main() error { 112 aggressive := flag.Bool("aggressive", false, "Aggressive deduplication including non pointers") 113 parse := flag.Bool("parse", true, "Parses source files to deduct types; use -parse=false to work around bugs in source parser") 114 rebase := flag.Bool("rebase", true, "Guess GOROOT and GOPATH") 115 verboseFlag := flag.Bool("v", false, "Enables verbose logging output") 116 filterFlag := flag.String("f", "", "Regexp to filter out headers that match, ex: -f 'IO wait|syscall'") 117 matchFlag := flag.String("m", "", "Regexp to filter by only headers that match, ex: -m 'semacquire'") 118 // Console only. 119 fullPath := flag.Bool("full-path", false, "Print full sources path") 120 noColor := flag.Bool("no-color", !isatty.IsTerminal(os.Stdout.Fd()) || os.Getenv("TERM") == "dumb", "Disable coloring") 121 forceColor := flag.Bool("force-color", false, "Forcibly enable coloring when with stdout is redirected") 122 // HTML only. 123 html := flag.String("html", "", "Output an HTML file") 124 flag.Parse() 125 126 log.SetFlags(log.Lmicroseconds) 127 if !*verboseFlag { 128 log.SetOutput(ioutil.Discard) 129 } 130 131 var err error 132 var filter *regexp.Regexp 133 if *filterFlag != "" { 134 if filter, err = regexp.Compile(*filterFlag); err != nil { 135 return err 136 } 137 } 138 139 var match *regexp.Regexp 140 if *matchFlag != "" { 141 if match, err = regexp.Compile(*matchFlag); err != nil { 142 return err 143 } 144 } 145 146 s := stack.AnyPointer 147 if *aggressive { 148 s = stack.AnyValue 149 } 150 151 var out io.Writer = os.Stdout 152 p := &defaultPalette 153 if *html == "" { 154 if *noColor && !*forceColor { 155 p = &Palette{} 156 } else { 157 out = colorable.NewColorableStdout() 158 } 159 } 160 161 var in *os.File 162 switch flag.NArg() { 163 case 0: 164 in = os.Stdin 165 // Explicitly silence SIGQUIT, as it is useful to gather the stack dump 166 // from the piped command.. 167 signals := make(chan os.Signal) 168 go func() { 169 for { 170 <-signals 171 } 172 }() 173 signal.Notify(signals, os.Interrupt, syscall.SIGQUIT) 174 175 case 1: 176 // Do not handle SIGQUIT when passed a file to process. 177 name := flag.Arg(0) 178 if in, err = os.Open(name); err != nil { 179 return fmt.Errorf("did you mean to specify a valid stack dump file name? %s", err) 180 } 181 defer in.Close() 182 183 default: 184 return errors.New("pipe from stdin or specify a single file") 185 } 186 return process(in, out, p, s, *fullPath, *parse, *rebase, *html, filter, match) 187} 188