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