|
@@ -1,7 +1,124 @@
|
|
|
package main
|
|
|
|
|
|
-import "github.com/gin-gonic/gin"
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ "fmt"
|
|
|
+ "github.com/gin-gonic/gin"
|
|
|
+ "io/ioutil"
|
|
|
+ "net/http"
|
|
|
+ "net/http/httputil"
|
|
|
+ "runtime"
|
|
|
+ "time"
|
|
|
+)
|
|
|
|
|
|
-func CustomRecoveryMidware() gin.HandlerFunc {
|
|
|
+var (
|
|
|
+ crmchr_dunno = []byte("???")
|
|
|
+ crmchr_centerDot = []byte("·")
|
|
|
+ crmchr_dot = []byte(".")
|
|
|
+ crmchr_slash = []byte("/")
|
|
|
+)
|
|
|
|
|
|
+func CustomAccessLogMidware(alogger *AccessLogger, elogger *CommonLogger) gin.HandlerFunc {
|
|
|
+ return func(context *gin.Context) {
|
|
|
+ start := time.Now()
|
|
|
+ path := context.Request.URL.Path
|
|
|
+ raw := context.Request.URL.RawQuery
|
|
|
+ context.Next()
|
|
|
+ end := time.Now()
|
|
|
+ latency := end.Sub(start)
|
|
|
+ clientIP := context.ClientIP()
|
|
|
+ method := context.Request.Method
|
|
|
+ statusCode := context.Writer.Status()
|
|
|
+ comment := context.Errors.ByType(gin.ErrorTypePrivate).String()
|
|
|
+ if raw != "" {
|
|
|
+ path = path + "?" + raw
|
|
|
+ }
|
|
|
+ alogger.Emit(start, statusCode, latency, clientIP, method, path)
|
|
|
+ if comment != "" {
|
|
|
+ elogger.EmitF(LogLv_WARN, "WebContext", "Web Context Error Info: %s", comment)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func CustomRecoveryMidware(logger *CommonLogger, isDebug bool) gin.HandlerFunc {
|
|
|
+ return func(context *gin.Context) {
|
|
|
+ defer func() {
|
|
|
+ if err := recover(); err != nil {
|
|
|
+ if logger != nil {
|
|
|
+ stack := stack(3)
|
|
|
+ if isDebug {
|
|
|
+ httprequest, _ := httputil.DumpRequest(context.Request, false)
|
|
|
+ msg := fmt.Sprintf("Panic recovered from error '%s':\n%s\n%s", err, string(httprequest), stack)
|
|
|
+ logger.Emit(LogLv_PANIC, "Recovery", msg)
|
|
|
+ } else {
|
|
|
+ msg := fmt.Sprintf("Panic recovered from error '%s':\n%s", err, stack)
|
|
|
+ logger.Emit(LogLv_PANIC, "Recovery", msg)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ context.AbortWithStatus(http.StatusInternalServerError)
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ context.Next()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// stack returns a nicely formatted stack frame, skipping skip frames.
|
|
|
+func stack(skip int) []byte {
|
|
|
+ buf := new(bytes.Buffer) // the returned data
|
|
|
+ // As we loop, we open files and read them. These variables record the currently
|
|
|
+ // loaded file.
|
|
|
+ var lines [][]byte
|
|
|
+ var lastFile string
|
|
|
+ for i := skip; ; i++ { // Skip the expected number of frames
|
|
|
+ pc, file, line, ok := runtime.Caller(i)
|
|
|
+ if !ok {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ // Print this much at least. If we can't find the source, it won't show.
|
|
|
+ fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
|
|
|
+ if file != lastFile {
|
|
|
+ data, err := ioutil.ReadFile(file)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ lines = bytes.Split(data, []byte{'\n'})
|
|
|
+ lastFile = file
|
|
|
+ }
|
|
|
+ fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
|
|
|
+ }
|
|
|
+ return buf.Bytes()
|
|
|
+}
|
|
|
+
|
|
|
+// source returns a space-trimmed slice of the n'th line.
|
|
|
+func source(lines [][]byte, n int) []byte {
|
|
|
+ n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
|
|
|
+ if n < 0 || n >= len(lines) {
|
|
|
+ return crmchr_dunno
|
|
|
+ }
|
|
|
+ return bytes.TrimSpace(lines[n])
|
|
|
+}
|
|
|
+
|
|
|
+// function returns, if possible, the name of the function containing the PC.
|
|
|
+func function(pc uintptr) []byte {
|
|
|
+ fn := runtime.FuncForPC(pc)
|
|
|
+ if fn == nil {
|
|
|
+ return crmchr_dunno
|
|
|
+ }
|
|
|
+ name := []byte(fn.Name())
|
|
|
+ // The name includes the path name to the package, which is unnecessary
|
|
|
+ // since the file name is already included. Plus, it has center dots.
|
|
|
+ // That is, we see
|
|
|
+ // runtime/debug.*T·ptrmethod
|
|
|
+ // and want
|
|
|
+ // *T.ptrmethod
|
|
|
+ // Also the package path might contains dot (e.g. code.google.com/...),
|
|
|
+ // so first eliminate the path prefix
|
|
|
+ if lastslash := bytes.LastIndex(name, crmchr_slash); lastslash >= 0 {
|
|
|
+ name = name[lastslash+1:]
|
|
|
+ }
|
|
|
+ if period := bytes.Index(name, crmchr_dot); period >= 0 {
|
|
|
+ name = name[period+1:]
|
|
|
+ }
|
|
|
+ name = bytes.Replace(name, crmchr_centerDot, crmchr_dot, -1)
|
|
|
+ return name
|
|
|
}
|