github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/logging/panic.go (about) 1 package logging 2 3 import ( 4 "fmt" 5 "os" 6 "runtime/debug" 7 "strings" 8 "sync" 9 10 "github.com/hashicorp/go-hclog" 11 ) 12 13 // This output is shown if a panic happens. 14 const panicOutput = ` 15 !!!!!!!!!!!!!!!!!!!!!!!!!!! TERRAFORM CRASH !!!!!!!!!!!!!!!!!!!!!!!!!!!! 16 17 Terraform crashed! This is always indicative of a bug within Terraform. 18 Please report the crash with Terraform[1] so that we can fix this. 19 20 When reporting bugs, please include your terraform version, the stack trace 21 shown below, and any additional information which may help replicate the issue. 22 23 [1]: https://github.com/muratcelep/terraform/issues 24 25 !!!!!!!!!!!!!!!!!!!!!!!!!!! TERRAFORM CRASH !!!!!!!!!!!!!!!!!!!!!!!!!!!! 26 27 ` 28 29 // PanicHandler is called to recover from an not-internal panic in Terraform, and 30 // augments the standard stack trace with a more user friendly error message. 31 // PanicHandler must be called as a defered function, and must be the first 32 // defer called at the start of a new goroutine. 33 func PanicHandler() { 34 recovered := recover() 35 if recovered == nil { 36 return 37 } 38 39 fmt.Fprint(os.Stderr, panicOutput) 40 fmt.Fprint(os.Stderr, recovered) 41 42 // When called from a deferred function, debug.PrintStack will include the 43 // full stack from the point of the pending panic. 44 debug.PrintStack() 45 46 // An exit code of 11 keeps us out of the way of the detailed exitcodes 47 // from plan, and also happens to be the same code as SIGSEGV which is 48 // roughly the same type of condition that causes most panics. 49 os.Exit(11) 50 } 51 52 const pluginPanicOutput = ` 53 Stack trace from the %[1]s plugin: 54 55 %s 56 57 Error: The %[1]s plugin crashed! 58 59 This is always indicative of a bug within the plugin. It would be immensely 60 helpful if you could report the crash with the plugin's maintainers so that it 61 can be fixed. The output above should help diagnose the issue. 62 ` 63 64 // PluginPanics returns a series of provider panics that were collected during 65 // execution, and formatted for output. 66 func PluginPanics() []string { 67 return panics.allPanics() 68 } 69 70 // panicRecorder provides a registry to check for plugin panics that may have 71 // happened when a plugin suddenly terminates. 72 type panicRecorder struct { 73 sync.Mutex 74 75 // panics maps the plugin name to the panic output lines received from 76 // the logger. 77 panics map[string][]string 78 79 // maxLines is the max number of lines we'll record after seeing a 80 // panic header. Since this is going to be printed in the UI output, we 81 // don't want to destroy the scrollback. In most cases, the first few lines 82 // of the stack trace is all that are required. 83 maxLines int 84 } 85 86 // registerPlugin returns an accumulator function which will accept lines of 87 // a panic stack trace to collect into an error when requested. 88 func (p *panicRecorder) registerPlugin(name string) func(string) { 89 p.Lock() 90 defer p.Unlock() 91 92 // In most cases we shouldn't be starting a plugin if it already 93 // panicked, but clear out previous entries just in case. 94 delete(p.panics, name) 95 96 count := 0 97 98 // this callback is used by the logger to store panic output 99 return func(line string) { 100 p.Lock() 101 defer p.Unlock() 102 103 // stop recording if there are too many lines. 104 if count > p.maxLines { 105 return 106 } 107 count++ 108 109 p.panics[name] = append(p.panics[name], line) 110 } 111 } 112 113 func (p *panicRecorder) allPanics() []string { 114 p.Lock() 115 defer p.Unlock() 116 117 var res []string 118 for name, lines := range p.panics { 119 if len(lines) == 0 { 120 continue 121 } 122 123 res = append(res, fmt.Sprintf(pluginPanicOutput, name, strings.Join(lines, "\n"))) 124 } 125 return res 126 } 127 128 // logPanicWrapper wraps an hclog.Logger and intercepts and records any output 129 // that appears to be a panic. 130 type logPanicWrapper struct { 131 hclog.Logger 132 panicRecorder func(string) 133 inPanic bool 134 } 135 136 // go-plugin will create a new named logger for each plugin binary. 137 func (l *logPanicWrapper) Named(name string) hclog.Logger { 138 return &logPanicWrapper{ 139 Logger: l.Logger.Named(name), 140 panicRecorder: panics.registerPlugin(name), 141 } 142 } 143 144 // we only need to implement Debug, since that is the default output level used 145 // by go-plugin when encountering unstructured output on stderr. 146 func (l *logPanicWrapper) Debug(msg string, args ...interface{}) { 147 // We don't have access to the binary itself, so guess based on the stderr 148 // output if this is the start of the traceback. An occasional false 149 // positive shouldn't be a big deal, since this is only retrieved after an 150 // error of some sort. 151 152 panicPrefix := strings.HasPrefix(msg, "panic: ") || strings.HasPrefix(msg, "fatal error: ") 153 154 l.inPanic = l.inPanic || panicPrefix 155 156 if l.inPanic && l.panicRecorder != nil { 157 l.panicRecorder(msg) 158 } 159 160 l.Logger.Debug(msg, args...) 161 }