| ├── v1 # v1版本接口服務
| ├── system # 系統級服務
| └── enter.go # 統一入口
├── config # 配置相關
├── core # 核心模塊
├── dao # dao層
├── global # 全局變量
├── initialize # 配置啟動初始化
├── middleware # 中間件
├── model # 數據庫結構體
├── router # 路由
├── service # 業務層
├── utils # 工具函數
├── config.yaml # 配置文件
├── go.mod # 包管理
├── main.go # 項目啟動文件
└── README.md # 項目README

1.2 go.mod

使用以下命令初始化:

mkdir ewa_admin_server
cd ewa_admin_server

# init go.mod
go mod init ewa_admin_server

就會在根目錄下生成一個包管理配置文件 go.mod

一般來說,基本的后端服務都需要包括配置解析、日志、數據庫連接等流程,新建入口文件?main.go

package main

import "fmt"

func main() {
fmt.Println("hello world")

// TODO:1.配置初始化
// TODO:2.日志
// TODO:3.數據庫連接
// TODO:4.其他初始化
// TODO:5.啟動服務
}

1.3 引入 gin

安裝依賴

go get -u github.com/gin-gonic/gin

接著,我們可以試試用gin開啟一個服務:

package main

import (
"net/http"

"github.com/gin-gonic/gin"
)

func main() {
// TODO:1.配置初始化
// TODO:2.日志
// TODO:3.數據庫連接
// TODO:4.其他初始化
// TODO:5.啟動服務

r := gin.Default()

// 測試路由
r.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})

// 啟動服務器
r.Run(":8080")
}

啟動服務:

go run main.go

然后在瀏覽器中輸入?http://127.0.0.1:8080/ping,就會在頁面返回一個?pong

2、配置初始化 & 全局變量

2.1 viper簡介

配置文件是每個項目必不可少的部分,用來保存應用基本數據、數據庫配置等信息,避免要修改一個配置項需要到處找的尷尬。這里我使用?viper[1]?作為配置管理方案。

Viper 是 Go 語言中一個非常流行的配置管理庫,它可以幫助程序員在應用程序中加載和解析各種配置格式,如 JSON、YAML、TOML、INI 等。Viper 庫提供了一個簡單的接口,允許開發人員通過各種方法來訪問和管理配置。

下面是 Viper 庫的一些主要特點:

2.2 viper 基本使用

先安裝依賴:

go get -u github.com/spf13/viper 

然后在項目根目錄下的?config.yaml?文件中添加基本的配置信息:

app: # 基本配置信息
env: local # 環境
port: 8889 # 服務監聽端口
app_name: ewa_admin_server # 應用名稱
app_url: http://localhost # 應用域名
db_type: mysql # 使用的數據庫

在項目根目錄下新建文件夾?config,用于存放所有配置對應的結構體,新建?config.go?文件,定義?Configuration?結構體,其?App?屬性對應?config.yaml?中的?app

package config

type Configuration struct {
App App mapstructure:"app" json:"app" yaml:"app" }

新建?app.go?文件,定義 App 結構體,其所有屬性分別對應?config.yaml?中 app 下的所有配置

package config

type App struct {
Env string mapstructure:"env" json:"env" yaml:"env" Port int mapstructure:"port" json:"port" yaml:"port" AppName string mapstructure:"app_name" json:"app_name" yaml:"app_name" AppUrl string mapstructure:"app_url" json:"app_url" yaml:"app_url" DbType string mapstructure:"db_type" json:"db_type" yaml:"db_type" }

注意:配置結構體中 mapstructure 標簽需對應 config.ymal 中的配置名稱, viper 會根據標簽 value 值把 config.yaml 的數據賦予給結構體

2.3 將配置放入全局變量

為什么要將配置信息放入全局變量中?

在Go語言中,將配置信息存儲在全局變量中是一種常見的做法,這主要是因為全局變量的值可以在整個程序中訪問和共享,因此在某些情況下可以方便地進行配置管理和使用。

下面是一些常見的場景,可能會使用全局變量來存儲配置信息:

  1. 在應用程序的不同模塊中需要使用相同的配置信息時,使用全局變量可以方便地實現這一點。例如,在一個Web應用程序中,可能需要在多個處理程序中使用數據庫的連接信息,這時將連接信息存儲在全局變量中可以方便地在各個處理程序中使用。
  2. 在需要頻繁訪問配置信息的場景中,使用全局變量可以避免反復讀取配置文件或重復創建配置對象的開銷,提高程序的性能和效率。

不過,使用全局變量也可能會帶來一些潛在的問題,比如:

  1. 全局變量的值可以在整個程序中被修改,這可能會導致意外的行為和錯誤。
  2. 全局變量可能會使程序的依賴關系更加復雜和難以管理,因為它們可以被程序中的任何模塊訪問和修改。

因此,在使用全局變量存儲配置信息時,應該仔細考慮其對程序的影響,并確保采取適當的措施來減少潛在的問題。例如,可以使用只讀全局變量或使用鎖來保護全局變量的訪問。此外,也可以考慮使用依賴注入等技術來管理程序中的配置信息。

下面我們在?global?中創建一個?global.go?文件來集中存放全局變量:

package global

import (
"ewa_admin_server/config"

"github.com/spf13/viper"
)

var (
EWA_CONFIG config.Configuration
EWA_VIPER *viper.Viper
)

考慮實際工作中多環境開發、測試的場景,我們需要針對不同的環境使用不同的配置,在core中加入一個internal文件,添加一個constants.go,寫入:

考慮實際工作中多環境開發、測試的場景,我們需要針對不同的環境使用不同的配置,在core中加入一個internal文件,添加一個constants.go,寫入:

然后在?core?中新建?viper.go,編寫配置初始化方法:

package core

import (
"ewa_admin_server/core/internal"
"ewa_admin_server/global"
"flag"
"fmt"
"os"

"github.com/fsnotify/fsnotify"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)

// InitializeViper 優先級: 命令行 > 環境變量 > 默認值
func InitializeViper(path ...string) *viper.Viper {
var config string

if len(path) == 0 {
// 定義命令行flag參數,格式:flag.TypeVar(Type指針, flag名, 默認值, 幫助信息)
flag.StringVar(&config, "c", "", "choose config file.")

// 定義好命令行flag參數后,需要通過調用flag.Parse()來對命令行參數進行解析。
flag.Parse()

// 判斷命令行參數是否為空
if config == "" {
/*
判斷 internal.ConfigEnv 常量存儲的環境變量是否為空
比如我們啟動項目的時候,執行:GVA_CONFIG=config.yaml go run main.go
這時候 os.Getenv(internal.ConfigEnv) 得到的就是 config.yaml
當然,也可以通過 os.Setenv(internal.ConfigEnv, "config.yaml") 在初始化之前設置
*/
if configEnv := os.Getenv(internal.ConfigEnv); configEnv == "" {
switch gin.Mode() {
case gin.DebugMode:
config = internal.ConfigDefaultFile
fmt.Printf("您正在使用gin模式的%s環境名稱,config的路徑為%s\n", gin.EnvGinMode, internal.ConfigDefaultFile)
case gin.ReleaseMode:
config = internal.ConfigReleaseFile
fmt.Printf("您正在使用gin模式的%s環境名稱,config的路徑為%s\n", gin.EnvGinMode, internal.ConfigReleaseFile)
case gin.TestMode:
config = internal.ConfigTestFile
fmt.Printf("您正在使用gin模式的%s環境名稱,config的路徑為%s\n", gin.EnvGinMode, internal.ConfigTestFile)
}
} else {
// internal.ConfigEnv 常量存儲的環境變量不為空 將值賦值于config
config = configEnv
fmt.Printf("您正在使用%s環境變量,config的路徑為%s\n", internal.ConfigEnv, config)
}
} else {
// 命令行參數不為空 將值賦值于config
fmt.Printf("您正在使用命令行的-c參數傳遞的值,config的路徑為%s\n", config)
}
} else {
// 函數傳遞的可變參數的第一個值賦值于config
config = path[0]
fmt.Printf("您正在使用func Viper()傳遞的值,config的路徑為%s\n", config)
}

vip := viper.New()
vip.SetConfigFile(config)
vip.SetConfigType("yaml")
err := vip.ReadInConfig()
if err != nil {
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}

vip.WatchConfig()

vip.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("config file changed:", e.Name)
if err = vip.Unmarshal(&global.EWA_CONFIG); err != nil {
fmt.Println(err)
}
})

if err = vip.Unmarshal(&global.EWA_CONFIG); err != nil {
fmt.Println(err)
}

fmt.Println("====1-viper====: viper init config success")

return vip
}

重新啟動項目,就會在控制臺打印:

====1-viper====: viper init config success
====app_name====: ewa_admin_server

這里面涉及到幾個知識點:

2.3.1 命令行 flag

Go 提供了一個?flag?包,支持基本的命令行標志解析,請看下面的示例:

package main

import (
"flag"
"fmt"
)

func main() {

wordPtr := flag.String("word", "foo", "a string")

numbPtr := flag.Int("numb", 42, "an int")
forkPtr := flag.Bool("fork", false, "a bool")

var svar string
flag.StringVar(&svar, "svar", "bar", "a string var")

flag.Parse()

fmt.Println("word:", *wordPtr)
fmt.Println("numb:", *numbPtr)
fmt.Println("fork:", *forkPtr)
fmt.Println("svar:", svar)
fmt.Println("tail:", flag.Args())
}

使用:

$ go build command-line-flags.go

$ ./command-line-flags -word=opt -numb=7 -fork -svar=flag
word: opt
numb: 7
fork: true
svar: flag
tail: []

$ ./command-line-flags -word=opt
word: opt
numb: 42
fork: false
svar: bar
tail: []

$ ./command-line-flags -word=opt a1 a2 a3
word: opt
...
tail: [a1 a2 a3]

$ ./command-line-flags -word=opt a1 a2 a3 -numb=7
word: opt
numb: 42
fork: false
svar: bar
tail: [a1 a2 a3 -numb=7]

$ ./command-line-flags -h
Usage of ./command-line-flags:
-fork=false: a bool
-numb=42: an int
-svar="bar": a string var
-word="foo": a string

$ ./command-line-flags -wat
flag provided but not defined: -wat
Usage of ./command-line-flags:
...

2.3.2?os.Setenv()?&?os.Getenv()

package main

import (
"fmt"
"os"
"strings"
)

func main() {

os.Setenv("FOO", "1")
fmt.Println("FOO:", os.Getenv("FOO"))
fmt.Println("BAR:", os.Getenv("BAR"))

fmt.Println()
for _, e := range os.Environ() {
pair := strings.SplitN(e, "=", 2)
fmt.Println(pair[0])
}
}

使用:

$ go run environment-variables.go 
FOO: 1
BAR:

TERM_PROGRAM
PATH
SHELL

$ BAR=2 go run environment-variables.go
FOO: 1
BAR: 2
...

2.3.3 gin.Mode()

在初始化本路由的時候使用,從源碼可看出,通過給變量ginMode賦值的方式提供了三種模式:

DebugModeReleaseMode多了一些額外的錯誤信息,生產環境不需要這些信息。而TestModegin用于自己的單元測試,用來快速開關DebugMode。對其它開發者沒什么意義。可以通過gin.SetMode(AppMode)來設置mode。

需要注意的是:SetMode() 應該聲明在 gin.New() 前,否則配置無法更新:

關于viper的使用,最好是看官方文檔

2.4 使用配置

現在我們已經將配置解析到了全局變量中,就可以將其使用到服務啟動邏輯中了,在?core?中新建?server.go?文件,然后將服務啟動的方法寫在這里:

package core

import (
"ewa_admin_server/global"
"fmt"
"net/http"
"time"

"github.com/fvbock/endless"
"github.com/gin-gonic/gin"
)

type server interface {
ListenAndServe() error
}

func RunServer() {
r := gin.Default()

r.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})

address := fmt.Sprintf(":%d", global.EWA_CONFIG.App.Port)
s := initServer(address, r)

// 保證文本順序輸出
time.Sleep(10 * time.Microsecond)

fmt.Println(address, address) s.ListenAndServe() } func initServer(address string, router *gin.Engine) server { // 使用endless庫創建一個HTTP服務器,其中address是服務器的監聽地址(如:8080),router是HTTP請求路由器。 s := endless.NewServer(address, router) // 設置HTTP請求頭的讀取超時時間為20秒,如果在20秒內未讀取到請求頭,則會返回一個超時錯誤。 s.ReadHeaderTimeout = 20 * time.Second // 設置HTTP響應體的寫入超時時間為20秒,如果在20秒內未將響應體寫入完成,則會返回一個超時錯誤。 s.WriteTimeout = 20 * time.Second // 設置HTTP請求頭的最大字節數為1MB。如果請求頭超過1MB,則會返回一個錯誤。 s.MaxHeaderBytes = 1 << 20 return s }

使用 endless 的作用是實現無縫重載和優雅關閉 HTTP 服務器。自帶優雅地重啟或停止你的Web服務器,我們可以使用fvbock/endless[2]來替換默認的ListenAndServe,有關詳細信息,請參閱問題#296[3]

endless 是一個可以用于重新加載和優雅關閉HTTP服務器的庫。它可以在運行時更新服務器代碼而無需停止正在運行的HTTP服務器。這使得服務器能夠在生產環境下無縫地進行更新和維護,同時不影響當前正在運行的請求和連接。

使用 endless,可以在代碼修改后,通過發送信號量通知服務器進行重載,新的代碼會被加載并運行,舊的連接會繼續服務,新的連接將使用新的代碼進行處理。當需要關閉服務器時,endless 會等待所有當前處理的請求完成后再關閉服務器,這樣可以確保所有請求都能得到處理,避免數據丟失和用戶體驗下降。

在?Gin?中使用?endless?可以提高服務器的可靠性和穩定性,同時也能提高開發效率,減少服務器維護和更新的停機時間。

這些配置可以幫助我們優化HTTP服務器的性能和安全性。通過設置超時時間和最大字節數等參數,可以防止一些潛在的安全問題和性能問題。

例如,設置超時時間可以防止客戶端故意保持連接而導致的資源浪費,設置最大字節數可以防止客戶端發送過大的請求頭而導致的資源浪費和安全問題。

重啟項目,訪問?http://127.0.0.1:8889/ping,依然能在頁面看到?pong,就說明我們的初始化配置成功了。

3、日志初始化

為什么需要記錄日志?

Go語言中記錄日志是一個非常常見的行為。以下是幾個原因:

  1. 問題排查:在應用程序出現故障或錯誤時,日志記錄可以幫助我們定位問題所在。我們可以通過查看日志記錄,了解應用程序在何時、何處出現問題,從而更快地進行故障排查。
  2. 性能分析:日志記錄也可以用于分析應用程序的性能。我們可以在代碼中記錄應用程序執行過程中的關鍵事件和時間戳,然后使用這些信息來分析和優化應用程序的性能。
  3. 安全監控:日志記錄還可以用于監控應用程序的安全性。我們可以記錄所有的訪問請求和響應,以便在應用程序受到攻擊時能夠快速響應。
  4. 后續分析:日志記錄還可以用于后續分析和審計。我們可以將日志記錄存儲在外部數據庫中,以便在未來需要時能夠查詢和分析。

值得注意的是,日志記錄是一個非常重要的行為,可以幫助我們快速排查問題、優化性能、監控安全性以及進行后續分析和審計。因此,在Go語言中記錄日志是一個必要的步驟。

這里將使用 zap[4] 作為日志庫,一般來說,日志都是需要寫入到文件保存的,這也是 zap 唯一缺少的部分,所以我將結合 lumberjack[5] 來使用,實現日志切割歸檔的功能。使用Zap作為日志記錄器,有以下幾個原因:

  1. 高性能:Zap是一個非常高性能的日志庫,其性能比其他日志庫(如logrus和zap)更高。這主要是由于Zap使用了零內存分配的技術,可以在不影響性能的情況下減少垃圾回收。
  2. 豐富的功能:Zap支持多種日志級別、多種輸出格式(如JSON、Console、Logfmt等)、多種輸出位置(如控制臺、文件、網絡等),還支持自定義日志字段和Hook。
  3. 安全性:Zap采用了嚴格的日志記錄策略,確保日志安全性。它可以避免由于多協程寫入日志導致的競態條件,還可以自動切分日志文件,避免單個日志文件過大導致的性能問題和磁盤空間浪費。
  4. 社區支持:Zap是由Uber開發并維護的開源日志庫,其開發和維護的活躍度和社區支持度都非常高。因此,Zap可以得到廣泛的支持和使用,并有大量的第三方庫和框架與之兼容。

而Lumberjack是一個用于高性能、輪轉和滾動日志文件的庫。在Zap中使用Lumberjack有以下幾個好處:

  1. 高性能:Lumberjack使用了緩沖區和異步寫入等技術,可以減少IO的次數,提高寫入日志的性能。
  2. 輪轉和滾動:Lumberjack可以自動進行日志文件的輪轉和滾動,可以防止單個日志文件變得過大而影響性能,也可以方便地管理和備份歷史日志。
  3. 穩定性和可靠性:Lumberjack可以確保即使在寫入日志的同時發生了故障,也不會丟失日志數據。此外,Lumberjack還可以處理文件系統的錯誤和日志文件權限等問題。

在Zap中使用Lumberjack可以提高日志的性能、穩定性和可靠性,并且方便管理和備份歷史日志。

安裝zap:

go get -u go.uber.org/zap
go get -u gopkg.in/natefinch/lumberjack.v2

3.1 定義配置項

在?config.yarml?中新增日志配置:

# ...

zap: # 日志配置
level: info # 日志級別
prefix: '[east_white_common_admin/server]' # 日志前綴
format: console # 輸出
director: log # 日志存放的文件
encode_level: LowercaseColorLevelEncoder # 編碼級別
stacktrace_key: stacktrace # 棧名
max_age: 0 # 日志留存時間
show_line: true # 顯示行
log_in_console: true # 輸出控制臺

然后新建?config/zap.go,在文件中增加對應的結構體和日志級別轉換方法:

package config

import (
"strings"

"go.uber.org/zap/zapcore"
)

type Zap struct {
Level string mapstructure:"level" json:"level" yaml:"level" // 級別 Prefix string mapstructure:"prefix" json:"prefix" yaml:"prefix" // 日志前綴 Format string mapstructure:"format" json:"format" yaml:"format" // 輸出 Director string mapstructure:"director" json:"director" yaml:"director" // 日志文件夾 EncodeLevel string mapstructure:"encode_level" json:"encode_level" yaml:"encode_level" // 編碼級 StacktraceKey string mapstructure:"stacktrace_key" json:"stacktrace_key" yaml:"stacktrace_key" // 棧名 MaxAge int mapstructure:"max_age" json:"max_age" yaml:"max_age" // 日志留存時間 ShowLine bool mapstructure:"show_line" json:"show_line" yaml:"show_line" // 顯示行 LogInConsole bool mapstructure:"log_in_console" json:"log_in_console" yaml:"log_in_console" // 輸出控制臺 } // ZapEncodeLevel 根據 EncodeLevel 返回 zapcore.LevelEncoder func (z *Zap) ZapEncodeLevel() zapcore.LevelEncoder { switch { case z.EncodeLevel == "LowercaseLevelEncoder": // 小寫編碼器(默認) return zapcore.LowercaseLevelEncoder case z.EncodeLevel == "LowercaseColorLevelEncoder": // 小寫編碼器帶顏色 return zapcore.LowercaseColorLevelEncoder case z.EncodeLevel == "CapitalLevelEncoder": // 大寫編碼器 return zapcore.CapitalLevelEncoder case z.EncodeLevel == "CapitalColorLevelEncoder": // 大寫編碼器帶顏色 return zapcore.CapitalColorLevelEncoder default: return zapcore.LowercaseLevelEncoder } } // TransportLevel 根據字符串轉化為 zapcore.Level func (z *Zap) TransportLevel() zapcore.Level { z.Level = strings.ToLower(z.Level) switch z.Level { case "debug": return zapcore.DebugLevel case "info": return zapcore.InfoLevel case "warn": return zapcore.WarnLevel case "error": return zapcore.WarnLevel case "dpanic": return zapcore.DPanicLevel case "panic": return zapcore.PanicLevel case "fatal": return zapcore.FatalLevel default: return zapcore.DebugLevel } }

別忘了在?config.go?中加入?zap

package config

type Configuration struct {
App App mapstructure:"app" json:"app" yaml:"app" Zap Zap mapstructure:"zap" json:"zap" yaml:"zap" }

3.2 日志初始化方法

接下來就是寫日志初始化方法了,在?core?中新建?zap.go

package core

import (
"ewa_admin_server/core/internal"
"ewa_admin_server/global"
"ewa_admin_server/utils"
"fmt"

"os"

"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

// InitializeZap Zap 獲取 zap.Logger
func InitializeZap() (logger *zap.Logger) {
if ok, _ := utils.PathExists(global.EWA_CONFIG.Zap.Director); !ok { // 判斷是否有Director文件夾
fmt.Printf("create %v directory\n", global.EWA_CONFIG.Zap.Director)
_ = os.Mkdir(global.EWA_CONFIG.Zap.Director, os.ModePerm)
}

cores := internal.Zap.GetZapCores()
logger = zap.New(zapcore.NewTee(cores...))

if global.EWA_CONFIG.Zap.ShowLine {
logger = logger.WithOptions(zap.AddCaller())
}

fmt.Println("====2-zap====: zap log init success")
return logger
}

在?core/internal?中新建?zap.go

package internal

import (
"ewa_admin_server/global"
"fmt"
"time"

"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

var Zap = new(_zap)

type _zap struct{}

// GetEncoder 獲取 zapcore.Encoder
func (z *_zap) GetEncoder() zapcore.Encoder {
if global.EWA_CONFIG.Zap.Format == "json" {
return zapcore.NewJSONEncoder(z.GetEncoderConfig())
}
return zapcore.NewConsoleEncoder(z.GetEncoderConfig())
}

// GetEncoderConfig 獲取zapcore.EncoderConfig
func (z *_zap) GetEncoderConfig() zapcore.EncoderConfig {
return zapcore.EncoderConfig{
MessageKey: "message",
LevelKey: "level",
TimeKey: "time",
NameKey: "logger",
CallerKey: "caller",
StacktraceKey: global.EWA_CONFIG.Zap.StacktraceKey,
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: global.EWA_CONFIG.Zap.ZapEncodeLevel(),
EncodeTime: z.CustomTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.FullCallerEncoder,
}
}

// GetEncoderCore 獲取Encoder的 zapcore.Core
func (z *_zap) GetEncoderCore(l zapcore.Level, level zap.LevelEnablerFunc) zapcore.Core {
writer, err := FileRotateLogs.GetWriteSyncer(l.String()) // 使用file-rotatelogs進行日志分割
if err != nil {
fmt.Printf("Get Write Syncer Failed err:%v", err.Error())
return nil
}

return zapcore.NewCore(z.GetEncoder(), writer, level)
}

// CustomTimeEncoder 自定義日志輸出時間格式
func (z *_zap) CustomTimeEncoder(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
encoder.AppendString(global.EWA_CONFIG.Zap.Prefix + t.Format("2006/01/02 - 15:04:05.000"))
}

// GetZapCores 根據配置文件的Level獲取 []zapcore.Core
func (z *_zap) GetZapCores() []zapcore.Core {
cores := make([]zapcore.Core, 0, 7)
for level := global.EWA_CONFIG.Zap.TransportLevel(); level <= zapcore.FatalLevel; level++ {
cores = append(cores, z.GetEncoderCore(level, z.GetLevelPriority(level)))
}
return cores
}

// GetLevelPriority 根據 zapcore.Level 獲取 zap.LevelEnablerFunc
func (z *_zap) GetLevelPriority(level zapcore.Level) zap.LevelEnablerFunc {
switch level {
case zapcore.DebugLevel:
return func(level zapcore.Level) bool { // 調試級別
return level == zap.DebugLevel
}
case zapcore.InfoLevel:
return func(level zapcore.Level) bool { // 日志級別
return level == zap.InfoLevel
}
case zapcore.WarnLevel:
return func(level zapcore.Level) bool { // 警告級別
return level == zap.WarnLevel
}
case zapcore.ErrorLevel:
return func(level zapcore.Level) bool { // 錯誤級別
return level == zap.ErrorLevel
}
case zapcore.DPanicLevel:
return func(level zapcore.Level) bool { // dpanic級別
return level == zap.DPanicLevel
}
case zapcore.PanicLevel:
return func(level zapcore.Level) bool { // panic級別
return level == zap.PanicLevel
}
case zapcore.FatalLevel:
return func(level zapcore.Level) bool { // 終止級別
return level == zap.FatalLevel
}
default:
return func(level zapcore.Level) bool { // 調試級別
return level == zap.DebugLevel
}
}
}

以及?file_rotate_logs.go

package internal

import (
"ewa_admin_server/global"
"os"
"path"
"time"

rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"go.uber.org/zap/zapcore"
)

var FileRotateLogs = new(fileRotateLogs)

type fileRotateLogs struct{}

// GetWriteSyncer 獲取 zapcore.WriteSyncer
func (r *fileRotateLogs) GetWriteSyncer(level string) (zapcore.WriteSyncer, error) {
fileWriter, err := rotatelogs.New(
path.Join(global.EWA_CONFIG.Zap.Director, "%Y-%m-%d", level+".log"),
rotatelogs.WithClock(rotatelogs.Local),
rotatelogs.WithMaxAge(time.Duration(global.EWA_CONFIG.Zap.MaxAge)*24*time.Hour), // 日志留存時間
rotatelogs.WithRotationTime(time.Hour*24),
)
if global.EWA_CONFIG.Zap.LogInConsole {
return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileWriter)), err
}
return zapcore.AddSync(fileWriter), err
}

新建?utils/directory.go?文件,編寫?PathExists?函數,用于判斷路徑是否存在:

package utils

import (
"errors"
"os"
)

//@function: PathExists
//@description: 文件目錄是否存在
//@param: path string
//@return: bool, error

func PathExists(path string) (bool, error) {
fi, err := os.Stat(path)
if err == nil {
if fi.IsDir() {
return true, nil
}
return false, errors.New("存在同名文件")
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}

3.3 定義全局變量

在?global/global.go?中,添加?Log?成員屬性

package global

import (
"ewa_admin_server/config"

"go.uber.org/zap"

"github.com/spf13/viper"
)

var (
EWA_CONFIG config.Configuration
EWA_VIPER *viper.Viper
EWA_LOG *zap.Logger
)

3.4 測試

在?main.go?中調用日志初始化函數,并嘗試寫入日志:

package main

import (
"ewa_admin_server/core"
"ewa_admin_server/global"

"go.uber.org/zap"

"github.com/gin-gonic/gin"
)

const AppMode = "debug" // 運行環境,主要有三種:debug、test、release

func main() {
gin.SetMode(AppMode)

// TODO:1.配置初始化
global.EWA_VIPER = core.InitializeViper()

// TODO:2.日志
global.EWA_LOG = core.InitializeZap()
zap.ReplaceGlobals(global.EWA_LOG)

global.EWA_LOG.Info("server run success on ", zap.String("zap_log", "zap_log"))

// TODO:3.數據庫連接

// TODO:4.其他初始化

// TODO:5.啟動服務
core.RunServer()
}

重啟項目,就會發現在根目錄下生成了一個?log?文件夾,作為我們日后開發用的日志記錄文件。

本文章轉載微信公眾號@Go開發大全

上一篇:

Go高性能JSON庫:Sonic

下一篇:

深度學習入門系列:AlexNet和LeNet詳細介紹和實現
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費