forked from skeema/skeema
-
Notifications
You must be signed in to change notification settings - Fork 0
/
exit.go
158 lines (143 loc) · 4.22 KB
/
exit.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package main
import (
"errors"
"fmt"
"os"
"runtime"
"runtime/debug"
"strings"
log "github.com/sirupsen/logrus"
"github.com/skeema/skeema/internal/util"
)
// Constants representing some predefined exit codes used by Skeema. A few of
// these are loosely adapted from BSD's `man sysexits`.
const (
CodeSuccess = 0
CodeDifferencesFound = 1
CodePartialError = 1
CodeFatalError = 2
CodeBadUsage = 64
CodeBadInput = 65
CodeNoInput = 66
CodeCantCreate = 73
CodeBadConfig = 78
)
// ExitCoder is an interface for error values that also expose a specific
// process exit code.
type ExitCoder interface {
error
ExitCode() int
}
// ExitValue represents an exit code for an operation. It satisfies the Error
// interface, but does not necessarily indicate a "fatal error" condition. For
// example, diff exit code of 1 means differences were found; lint exit code of
// 1 means at least one file was reformatted. By convention, fatal errors will
// be indicated by a code > 1. A nil *ExitValue always represents success / exit
// code 0.
type ExitValue struct {
Code int
message string
}
// Error returns an error string, satisfying the Go builtin error interface.
func (ev *ExitValue) Error() string {
if ev == nil {
return ""
}
return ev.message
}
// ExitCode returns ev's Code, satisfying the ExitCoder interface.
func (ev *ExitValue) ExitCode() int {
if ev == nil {
return CodeSuccess
}
return ev.Code
}
// NewExitValue is a constructor for ExitValue.
func NewExitValue(code int, format string, a ...interface{}) *ExitValue {
return &ExitValue{
Code: code,
message: fmt.Sprintf(format, a...),
}
}
// ExitCode returns an exit code corresponding to the supplied error. If err
// is nil, code 0 (success) is returned. If err is an ExitCoder (or wraps one),
// its ExitCode is returned. Otherwise, exit 2 code (fatal error) is returned.
func ExitCode(err error) int {
if err == nil {
return CodeSuccess
}
var ec ExitCoder
if errors.As(err, &ec) {
return ec.ExitCode()
}
return CodeFatalError
}
// HighestExitCode returns whichever arg has the highest exit code. In cases of
// ties, earlier args take precedence over later args.
func HighestExitCode(errs ...error) error {
var highestErr error
var highestCode int
for _, err := range errs {
if code := ExitCode(err); code > highestCode {
highestErr, highestCode = err, code
}
}
return highestErr
}
// realExit performs any necessary cleanup and then exits the program.
func realExit(code int) {
// Gracefully close all connection pools, to avoid aborted connection counter/
// logging in some versions of MySQL
util.CloseCachedConnectionPools()
os.Exit(code)
}
// by default, we want Exit to call realExit, but tests can manipulate this.
var exitFunc = realExit
// Exit terminates the program with the appropriate exit code and log output.
func Exit(err error) {
exitCode := ExitCode(err)
if err == nil {
log.Debug("Exit code 0 (SUCCESS)")
} else {
message := err.Error()
if message != "" {
if exitCode >= CodeFatalError {
log.Error(message)
} else {
log.Warn(message)
}
}
log.Debugf("Exit code %d", exitCode)
}
exitFunc(exitCode)
}
// panicHandler can be called in a deferred function to recover from panics by
// displaying a user-friendly message and then exiting with code 2 (fatal
// error).
func panicHandler() {
if iface := recover(); iface != nil {
location := "unknown location"
pc := make([]uintptr, 10)
if n := runtime.Callers(2, pc); n > 0 {
pc = pc[:n] // remove invalid pcs before calling runtime.CallersFrames
frames := runtime.CallersFrames(pc)
for {
frame, more := frames.Next()
if !strings.Contains(frame.File, "runtime/") {
location = fmt.Sprintf("%s at %s:%d", frame.Function, frame.File, frame.Line)
break
}
if !more {
break
}
}
}
log.Debug(string(debug.Stack()))
messages := []string{
fmt.Sprintf("Uncaught panic in %s: %v", location, iface),
"This situation indicates a bug in Skeema. Use --debug to view full stack trace.",
"Please file an issue report at https://github.com/skeema/skeema/issues with any available background information.",
}
Exit(NewExitValue(CodeFatalError, strings.Join(messages, "\n")))
}
}