github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/logging/panic.go (about) 1 package logging 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "os" 8 "strings" 9 "sync" 10 11 "github.com/hashicorp/go-hclog" 12 "github.com/mitchellh/panicwrap" 13 ) 14 15 // This output is shown if a panic happens. 16 const panicOutput = ` 17 18 !!!!!!!!!!!!!!!!!!!!!!!!!!! TERRAFORM CRASH !!!!!!!!!!!!!!!!!!!!!!!!!!!! 19 20 Terraform crashed! This is always indicative of a bug within Terraform. 21 A crash log has been placed at %[1]q relative to your current 22 working directory. It would be immensely helpful if you could please 23 report the crash with Terraform[1] so that we can fix this. 24 25 When reporting bugs, please include your terraform version. That 26 information is available on the first line of crash.log. You can also 27 get it by running 'terraform --version' on the command line. 28 29 SECURITY WARNING: the %[1]q file that was created may contain 30 sensitive information that must be redacted before it is safe to share 31 on the issue tracker. 32 33 [1]: https://github.com/hashicorp/terraform/issues 34 35 !!!!!!!!!!!!!!!!!!!!!!!!!!! TERRAFORM CRASH !!!!!!!!!!!!!!!!!!!!!!!!!!!! 36 ` 37 38 // panicHandler is what is called by panicwrap when a panic is encountered 39 // within Terraform. It is guaranteed to run after the resulting process has 40 // exited so we can take the log file, add in the panic, and store it 41 // somewhere locally. 42 func PanicHandler(tmpLogPath string) panicwrap.HandlerFunc { 43 return func(m string) { 44 // Create the crash log file where we'll write the logs 45 f, err := ioutil.TempFile(".", "crash.*.log") 46 if err != nil { 47 fmt.Fprintf(os.Stderr, "Failed to create crash log file: %s", err) 48 return 49 } 50 defer f.Close() 51 52 tmpLog, err := os.Open(tmpLogPath) 53 if err != nil { 54 fmt.Fprintf(os.Stderr, "Failed to open log file %q: %v\n", tmpLogPath, err) 55 return 56 } 57 defer tmpLog.Close() 58 59 // Copy the contents to the crash file. This will include 60 // the panic that just happened. 61 if _, err = io.Copy(f, tmpLog); err != nil { 62 fmt.Fprintf(os.Stderr, "Failed to write crash log: %s", err) 63 return 64 } 65 66 // add the trace back to the log 67 f.WriteString("\n" + m) 68 69 // Tell the user a crash occurred in some helpful way that 70 // they'll hopefully notice. 71 fmt.Printf("\n\n") 72 fmt.Printf(panicOutput, f.Name()) 73 } 74 } 75 76 const pluginPanicOutput = ` 77 Stack trace from the %[1]s plugin: 78 79 %s 80 81 Error: The %[1]s plugin crashed! 82 83 This is always indicative of a bug within the plugin. It would be immensely 84 helpful if you could report the crash with the plugin's maintainers so that it 85 can be fixed. The output above should help diagnose the issue. 86 ` 87 88 // PluginPanics returns a series of provider panics that were collected during 89 // execution, and formatted for output. 90 func PluginPanics() []string { 91 return panics.allPanics() 92 } 93 94 // panicRecorder provides a registry to check for plugin panics that may have 95 // happened when a plugin suddenly terminates. 96 type panicRecorder struct { 97 sync.Mutex 98 99 // panics maps the plugin name to the panic output lines received from 100 // the logger. 101 panics map[string][]string 102 103 // maxLines is the max number of lines we'll record after seeing a 104 // panic header. Since this is going to be printed in the UI output, we 105 // don't want to destroy the scrollback. In most cases, the first few lines 106 // of the stack trace is all that are required. 107 maxLines int 108 } 109 110 // registerPlugin returns an accumulator function which will accept lines of 111 // a panic stack trace to collect into an error when requested. 112 func (p *panicRecorder) registerPlugin(name string) func(string) { 113 p.Lock() 114 defer p.Unlock() 115 116 // In most cases we shouldn't be starting a plugin if it already 117 // panicked, but clear out previous entries just in case. 118 delete(p.panics, name) 119 120 count := 0 121 122 // this callback is used by the logger to store panic output 123 return func(line string) { 124 p.Lock() 125 defer p.Unlock() 126 127 // stop recording if there are too many lines. 128 if count > p.maxLines { 129 return 130 } 131 count++ 132 133 p.panics[name] = append(p.panics[name], line) 134 } 135 } 136 137 func (p *panicRecorder) allPanics() []string { 138 p.Lock() 139 defer p.Unlock() 140 141 var res []string 142 for name, lines := range p.panics { 143 if len(lines) == 0 { 144 continue 145 } 146 147 res = append(res, fmt.Sprintf(pluginPanicOutput, name, strings.Join(lines, "\n"))) 148 } 149 return res 150 } 151 152 // logPanicWrapper wraps an hclog.Logger and intercepts and records any output 153 // that appears to be a panic. 154 type logPanicWrapper struct { 155 hclog.Logger 156 panicRecorder func(string) 157 inPanic bool 158 } 159 160 // go-plugin will create a new named logger for each plugin binary. 161 func (l *logPanicWrapper) Named(name string) hclog.Logger { 162 return &logPanicWrapper{ 163 Logger: l.Logger.Named(name), 164 panicRecorder: panics.registerPlugin(name), 165 } 166 } 167 168 // we only need to implement Debug, since that is the default output level used 169 // by go-plugin when encountering unstructured output on stderr. 170 func (l *logPanicWrapper) Debug(msg string, args ...interface{}) { 171 // We don't have access to the binary itself, so guess based on the stderr 172 // output if this is the start of the traceback. An occasional false 173 // positive shouldn't be a big deal, since this is only retrieved after an 174 // error of some sort. 175 176 panicPrefix := strings.HasPrefix(msg, "panic: ") || strings.HasPrefix(msg, "fatal error: ") 177 178 l.inPanic = l.inPanic || panicPrefix 179 180 if l.inPanic && l.panicRecorder != nil { 181 l.panicRecorder(msg) 182 } 183 184 // If we have logging turned on, we need to prevent panicwrap from seeing 185 // this as a core panic. This can be done by obfuscating the panic error 186 // line. 187 if panicPrefix { 188 colon := strings.Index(msg, ":") 189 msg = strings.ToUpper(msg[:colon]) + msg[colon:] 190 } 191 192 l.Logger.Debug(msg, args...) 193 }