gitee.com/woood2/luca@v1.0.4/cmd/backend/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	_ "gitee.com/woood2/luca/cmd/backend/docs"
     7  	"gitee.com/woood2/luca/cmd/backend/internal/assembly"
     8  	"gitee.com/woood2/luca/internal/cache"
     9  	"gitee.com/woood2/luca/internal/conf"
    10  	"gitee.com/woood2/luca/internal/db"
    11  	"gitee.com/woood2/luca/internal/discovery"
    12  	"gitee.com/woood2/luca/internal/errcode"
    13  	myLog "gitee.com/woood2/luca/internal/log"
    14  	"gitee.com/woood2/luca/internal/producer"
    15  	"gitee.com/woood2/luca/internal/status"
    16  	"gitee.com/woood2/luca/internal/trace"
    17  	"github.com/gin-contrib/cors"
    18  	"github.com/gin-gonic/gin"
    19  	"github.com/go-kit/kit/sd"
    20  	opzipkin "github.com/openzipkin/zipkin-go"
    21  	swaggerFiles "github.com/swaggo/files"
    22  	ginSwagger "github.com/swaggo/gin-swagger"
    23  	"go.uber.org/zap"
    24  	"log"
    25  	"net"
    26  	"net/http"
    27  	"net/http/httputil"
    28  	"os"
    29  	"os/signal"
    30  	"runtime/debug"
    31  	"strconv"
    32  	"strings"
    33  	"syscall"
    34  	"time"
    35  )
    36  
    37  const entrance = "backend"
    38  
    39  // @title Luca APIs
    40  // @version 1.0
    41  // @description 基于Gin封装的demo项目
    42  // @securityDefinitions.apikey ApiKeyAuth
    43  // @in header
    44  // @name token
    45  // @BasePath /
    46  func main() {
    47  	//config
    48  	attr := conf.Load("application.yml", "configs/application.yml")
    49  	//consul
    50  	client, consulClient := discovery.Client(attr.Consul.Host, attr.Consul.Port)
    51  	conf.MergeConsul(attr, consulClient)
    52  	//zap logger
    53  	logger := myLog.Build(attr.Env, attr.Project, entrance, attr.Host, attr.ConsoleLog)
    54  	defer logger.Sync()
    55  	//gorm & mongo & redis & kafka
    56  	gormDB := db.NewGormDB(attr.Env, attr.Mysql, logger)
    57  	db.SetGlobalGormDB(gormDB)
    58  	mongoDB := db.NewMongoDB(attr.Mongo)
    59  	db.SetMongoDB(mongoDB)
    60  	redisCache := cache.NewRedis(attr.Redis)
    61  	cache.SetRedis(redisCache)
    62  	p := producer.New(attr.Kafka, logger)
    63  	defer p.Close()
    64  	producer.SetG(p)
    65  	//zipkin
    66  	trace.Open(attr.Zipkin)
    67  	defer trace.Close()
    68  	//sdk
    69  	assembly.SetupSDK(client, logger)
    70  	//register
    71  	var registar sd.Registrar
    72  	if attr.Backend.Register {
    73  		ho := strings.Split(attr.Backend.Addr, ":")
    74  		if port, err := strconv.Atoi(ho[1]); err != nil {
    75  			log.Panicf("attr.Backend.Addr error: %s\n", err)
    76  		} else {
    77  			registar = discovery.RegisterRestful(client, attr.Project+"_"+entrance, attr.Host, port)
    78  			registar.Register()
    79  		}
    80  	}
    81  	//gin
    82  	if conf.IsPro(attr.Env) {
    83  		gin.SetMode(gin.ReleaseMode)
    84  	}
    85  	r := gin.New()
    86  	r.Use(loggerWithZap(logger, time.RFC3339, false))
    87  	r.Use(recoveryWithZap(logger, true, attr.Env))
    88  	config := cors.DefaultConfig()
    89  	config.AllowOrigins = attr.Backend.AllowOrigins
    90  	r.Use(cors.New(config))
    91  	//Pprof & prometheus & hystrix
    92  	go status.Pprof(attr.Pprof, attr.Env, attr.Backend.PprofAddr)
    93  	go status.Prometheus(attr.Backend.MetricsAddr)
    94  	go status.Hystrix(attr.Backend.HystrixPort)
    95  	//swagger
    96  	if !conf.IsPro(attr.Env) {
    97  		url := ginSwagger.URL("http://" + attr.Host + attr.Backend.Addr + "/swagger/doc.json")
    98  		r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
    99  	}
   100  	//handler
   101  	assembly.RegisterHandler(attr.Project, entrance, attr.Env, r, logger, gormDB)
   102  	//run
   103  	srv := &http.Server{
   104  		Addr:    attr.Backend.Addr,
   105  		Handler: r,
   106  	}
   107  	go func() {
   108  		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
   109  			log.Panicf("listen: %s\n", err)
   110  		}
   111  	}()
   112  	//graceful shutdown
   113  	quit := make(chan os.Signal)
   114  	signal.Notify(quit, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)
   115  	<-quit
   116  	log.Println("Shutting down server...")
   117  	if attr.Backend.Register {
   118  		registar.Deregister()       //gray release
   119  		time.Sleep(1 * time.Second) //make sure
   120  	}
   121  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   122  	defer cancel()
   123  	if err := srv.Shutdown(ctx); err != nil {
   124  		log.Panicln("Server forced to shutdown:", err)
   125  	}
   126  	log.Println("Server exiting")
   127  }
   128  
   129  func loggerWithZap(logger *zap.Logger, timeFormat string, utc bool) gin.HandlerFunc {
   130  	return func(c *gin.Context) {
   131  		start := time.Now()
   132  		// some evil middlewares modify this values
   133  		path := c.Request.URL.Path
   134  		query := c.Request.URL.RawQuery
   135  		c.Next()
   136  
   137  		end := time.Now()
   138  		latency := end.Sub(start)
   139  		if utc {
   140  			end = end.UTC()
   141  		}
   142  
   143  		if len(c.Errors) > 0 {
   144  			// Append error field if this is an erroneous request.
   145  			for _, e := range c.Errors.Errors() {
   146  				logger.Error(e)
   147  			}
   148  		} else {
   149  			traceID := "-"
   150  			if s, ok := c.Keys[trace.SpanKey()]; ok {
   151  				span := s.(opzipkin.Span)
   152  				traceID = span.Context().TraceID.String()
   153  			}
   154  
   155  			logger.Info(path,
   156  				zap.Int("status", c.Writer.Status()),
   157  				zap.String("method", c.Request.Method),
   158  				zap.String("path", path),
   159  				zap.String("traceID", traceID),
   160  				zap.String("query", query),
   161  				zap.String("ip", c.ClientIP()),
   162  				zap.String("user-agent", c.Request.UserAgent()),
   163  				zap.String("time", end.Format(timeFormat)),
   164  				zap.Duration("latency", latency),
   165  			)
   166  		}
   167  	}
   168  }
   169  
   170  func recoveryWithZap(logger *zap.Logger, stack bool, env string) gin.HandlerFunc {
   171  	return func(c *gin.Context) {
   172  		defer func() {
   173  			if err := recover(); err != nil {
   174  				// Check for a broken connection, as it is not really a
   175  				// condition that warrants a panic stack trace.
   176  				var brokenPipe bool
   177  				if ne, ok := err.(*net.OpError); ok {
   178  					if se, ok := ne.Err.(*os.SyscallError); ok {
   179  						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
   180  							brokenPipe = true
   181  						}
   182  					}
   183  				}
   184  
   185  				httpRequest, _ := httputil.DumpRequest(c.Request, false)
   186  				if brokenPipe {
   187  					logger.Error(c.Request.URL.Path,
   188  						zap.Any("error", err),
   189  						zap.String("request", string(httpRequest)),
   190  					)
   191  					// If the connection is dead, we can't write a status to it.
   192  					c.Error(err.(error)) // nolint: errcheck
   193  					c.Abort()
   194  					return
   195  				}
   196  
   197  				traceID := "-"
   198  				if s, ok := c.Keys[trace.SpanKey()]; ok {
   199  					span := s.(opzipkin.Span)
   200  					traceID = span.Context().TraceID.String()
   201  				}
   202  
   203  				detail := string(debug.Stack())
   204  				if stack {
   205  					logger.Error("[Recovery from panic]",
   206  						zap.Time("time", time.Now()),
   207  						zap.Any("error", err),
   208  						zap.String("traceID", traceID),
   209  						zap.String("request", string(httpRequest)),
   210  						zap.String("stack", detail),
   211  					)
   212  				} else {
   213  					logger.Error("[Recovery from panic]",
   214  						zap.Time("time", time.Now()),
   215  						zap.Any("error", err),
   216  						zap.String("traceID", traceID),
   217  						zap.String("request", string(httpRequest)),
   218  					)
   219  				}
   220  				resp := &errcode.Resp{
   221  					ErrCode: errcode.RestfulErrCode[errcode.ServerErr],
   222  					ErrMsg:  errcode.ServerErr.Error(),
   223  					Data:    nil,
   224  					TraceID: traceID,
   225  				}
   226  				if !conf.IsPro(env) && stack {
   227  					resp.Stack = fmt.Sprintf("Recovery from panic: %+v", err)
   228  					resp.Stack += "\n" + detail
   229  				}
   230  				c.JSON(500, resp)
   231  				c.Abort()
   232  			}
   233  		}()
   234  		c.Next()
   235  	}
   236  }