Browse Source

新增zap日志库的支持,并提供Recovery的中间件

SliverHorn 4 years ago
parent
commit
acc0fc8d8f
6 changed files with 151 additions and 1 deletions
  1. 9 1
      server/config.yaml
  2. 9 0
      server/config/config.go
  3. 67 0
      server/core/zap.go
  4. 3 0
      server/global/global.go
  5. 3 0
      server/go.mod
  6. 60 0
      server/middleware/error.go

+ 9 - 1
server/config.yaml

@@ -64,4 +64,12 @@ log:
   prefix: '[GIN-VUE-ADMIN]'
   log-file: true
   stdout: 'DEBUG'
-  file: 'DEBUG'
+  file: 'DEBUG'
+
+# zap logger configuration
+zap:
+  level: "debug"
+  file: "DEBUG"
+  max_size: 200
+  max_age: 30
+  max_backups: 7

+ 9 - 0
server/config/config.go

@@ -10,6 +10,7 @@ type Server struct {
 	JWT       JWT       `mapstructure:"jwt" json:"jwt" yaml:"jwt"`
 	Captcha   Captcha   `mapstructure:"captcha" json:"captcha" yaml:"captcha"`
 	Log       Log       `mapstructure:"log" json:"log" yaml:"log"`
+	Zap    	  Zap       `mapstructure:"zap" json:"zap" yaml:"zap"`
 	LocalUpload LocalUpload `mapstructure:"localUpload" json:"localUpload" yaml:"localUpload"`
 }
 
@@ -78,3 +79,11 @@ type Sqlite struct {
 	Config   string `mapstructure:"config" json:"config" yaml:"config"`
 	LogMode  bool   `mapstructure:"log-mode" json:"logMode" yaml:"log-mode"`
 }
+
+type Zap struct {
+	Level      string `json:"level"`
+	File   	   string `json:"file"`
+	MaxSize    int    `json:"maxsize"`
+	MaxAge     int    `json:"max_age"`
+	MaxBackups int    `json:"max_backups"`
+}

+ 67 - 0
server/core/zap.go

@@ -0,0 +1,67 @@
+package core
+
+import (
+	"fmt"
+	"gin-vue-admin/global"
+	"gin-vue-admin/utils"
+	zaprotatelogs "github.com/lestrrat-go/file-rotatelogs"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+	"os"
+	"time"
+)
+
+const (
+	zapLogDir      = "log"
+	zapLogSoftLink = "latest_log"
+	zapModule      = "gin-vue-admin"
+)
+
+func init() {
+	if global.GVA_CONFIG.Zap.File == "" {
+		global.GVA_CONFIG.Zap.File = "DEBUG"
+	}
+	if ok, _ := utils.PathExists(zapLogDir); !ok {
+		// directory not exist
+		fmt.Println("create log directory")
+		_ = os.Mkdir(zapLogDir, os.ModePerm)
+	}
+	var l = new(zapcore.Level)
+	writeSyncer, err := getWriteSyncer()
+	if err != nil {
+		fmt.Printf("Get Write Syncer Failed err:%v", err.Error())
+		return
+	}
+	encoder := getEncoderConfig()
+	if err := l.UnmarshalText([]byte(global.GVA_CONFIG.Zap.Level)); err != nil {
+		fmt.Printf("Unmarshal Level Failed err:%v", err.Error())
+		return
+	}
+	core := zapcore.NewCore(encoder, writeSyncer, l)
+	global.GVA_ZAP = zap.New(core, zap.AddCaller())
+}
+
+// getWriteSyncer zap logger中加入file-rotatelogs
+func getWriteSyncer() (zapcore.WriteSyncer, error) {
+	fileWriter, err := zaprotatelogs.New(
+		zapLogDir + string(os.PathSeparator) + "%Y-%m-%d-%H-%M.log",
+		zaprotatelogs.WithLinkName(zapLogSoftLink),
+		zaprotatelogs.WithMaxAge(7*24*time.Hour),
+		zaprotatelogs.WithRotationTime(24*time.Hour),
+	)
+	return zapcore.AddSync(fileWriter), err
+}
+
+// getEncoderConfig 获取zapcore.Encoder
+func getEncoderConfig() zapcore.Encoder {
+	encoderConfig := zap.NewProductionEncoderConfig()
+	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
+	encoderConfig.TimeKey = "time"
+	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
+	encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
+	encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
+	return zapcore.NewConsoleEncoder(encoderConfig)
+}
+
+
+

+ 3 - 0
server/global/global.go

@@ -1,6 +1,8 @@
 package global
 
 import (
+	"go.uber.org/zap"
+
 	"gin-vue-admin/config"
 	"github.com/go-redis/redis"
 	oplogging "github.com/op/go-logging"
@@ -14,4 +16,5 @@ var (
 	GVA_CONFIG config.Server
 	GVA_VP     *viper.Viper
 	GVA_LOG    *oplogging.Logger
+	GVA_ZAP    *zap.Logger
 )

+ 3 - 0
server/go.mod

@@ -20,6 +20,8 @@ require (
 	github.com/golang/protobuf v1.4.2 // indirect
 	github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect
 	github.com/json-iterator/go v1.1.10 // indirect
+	github.com/lestrrat-go/file-rotatelogs v2.3.0+incompatible
+	github.com/lestrrat-go/strftime v1.0.3 // indirect
 	github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 // indirect
 	github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f
 	github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect
@@ -43,6 +45,7 @@ require (
 	github.com/tebeka/strftime v0.1.3 // indirect
 	github.com/unrolled/secure v1.0.7
 	github.com/urfave/cli v1.22.2 // indirect
+	go.uber.org/zap v1.10.0
 	golang.org/x/net v0.0.0-20200320220750-118fecf932d8 // indirect
 	golang.org/x/sys v0.0.0-20200610111108-226ff32320da // indirect
 	golang.org/x/tools v0.0.0-20200324003944-a576cf524670 // indirect

+ 60 - 0
server/middleware/error.go

@@ -0,0 +1,60 @@
+package middleware
+
+import (
+	"gin-vue-admin/global"
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"net"
+	"net/http"
+	"net/http/httputil"
+	"os"
+	"runtime/debug"
+	"strings"
+)
+
+// GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志
+func GinRecovery(stack bool) gin.HandlerFunc {
+	return func(c *gin.Context) {
+		defer func() {
+			if err := recover(); err != nil {
+				// Check for a broken connection, as it is not really a
+				// condition that warrants a panic stack trace.
+				var brokenPipe bool
+				if ne, ok := err.(*net.OpError); ok {
+					if se, ok := ne.Err.(*os.SyscallError); ok {
+						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
+							brokenPipe = true
+						}
+					}
+				}
+
+				httpRequest, _ := httputil.DumpRequest(c.Request, false)
+				if brokenPipe {
+					global.GVA_ZAP.Error(c.Request.URL.Path,
+						zap.Any("error", err),
+						zap.String("request", string(httpRequest)),
+					)
+					// If the connection is dead, we can't write a status to it.
+					_ = c.Error(err.(error)) // nolint: errcheck
+					c.Abort()
+					return
+				}
+
+				if stack {
+					global.GVA_ZAP.Error("[Recovery from panic]",
+						zap.Any("error", err),
+						zap.String("request", string(httpRequest)),
+						zap.String("stack", string(debug.Stack())),
+					)
+				} else {
+					global.GVA_ZAP.Error("[Recovery from panic]",
+						zap.Any("error", err),
+						zap.String("request", string(httpRequest)),
+					)
+				}
+				c.AbortWithStatus(http.StatusInternalServerError)
+			}
+		}()
+		c.Next()
+	}
+}