zap日志库的使用
安装
go get -u go.uber.org/zap
配置Zap logger
zap提供了两种类型的日志记录器——Sugard Logger和Logger
Logger
var logger *zap.Logger
// 初始化
func InitLogger() {
logger, _ = zap.NewProduction()
}
// 模拟请求发送
func simpleHttpGet(url string) {
resp, err := http.Get(url)
if err != nil {
//日志级别
logger.Error(
"Error fetching url",
zap.String("url", url),
zap.Error(err))
} else {
logger.Info("Success",
zap.String("statusCade", resp.Status),
zap.String("url", url))
resp.Body.Close()
}
}
func main() {
InitLogger()
//在程序调用结束后,把日志记录到磁盘中
defer logger.Sync()
simpleHttpGet("www.bilibili.com")
simpleHttpGet("https://www.bilibili.com")
}
定制Logger
将日志写入文件
使用zap.New()需要传入一个必须的配置
func New(core zapcore.Core, options ...Option) *Logger
zapcore.Core需要三个配置——Encoder,WriteSyncer,LogLevel。
Encoder:编码器(如何写入日志)。我们将使用开箱即用的New.JSONEncoder(),并使用预先设置的ProductionEncoderConfig()。
WriterSyncer:指定日志将写到哪里去。我们使用zapcore. AddSync()函数并且将打开的文件句柄传进去。
LogLevel:哪种级别的日志将被写入。
我们将修改上述部分中的Logger代码,并重写InitLogger() 方法。其余方法-main()/ SimpleHttpGet()保持不变。
package main
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"net/http"
"os"
)
var logger1 *zap.Logger
var sugarLogger *zap.SugaredLogger
// 初始化
func InitLogger1() {
//logger, _ = zap.NewProduction()
writeSyncer := getLogWriter()
encoder := getEncoder()
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
//zap.AddCaller()添加日志的调用信息,即哪个方法调用了日志
logger1 = zap.New(core,zap.AddCaller())
sugarLogger = logger1.Sugar()
}
func getEncoder() zapcore.Encoder {
encoderConfig := zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
FunctionKey: zapcore.OmitKey,
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
//将新生成的配置项传进去
return zapcore.NewConsoleEncoder(encoderConfig)
//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}
func getLogWriter() zapcore.WriteSyncer {
//file, _ := os.Create("./test.log")每次执行都会新建文件
//执行会往原有文件里面追加
file, _ := os.OpenFile("./test.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0744)
return zapcore.AddSync(file)
}
// 模拟请求发送
func simpleHttpGet1(url string) {
resp, err := http.Get(url)
if err != nil {
//日志级别
sugarLogger.Error(
"Error fetching url",
zap.String("url", url),
zap.Error(err))
} else {
sugarLogger.Info("Success",
zap.String("statusCade", resp.Status),
zap.String("url", url))
resp.Body.Close()
}
}
func main() {
InitLogger1()
//在程序调用结束后,把日志记录到磁盘中
defer logger1.Sync()
simpleHttpGet1("www.bilibili.com")
simpleHttpGet1("https://www.bilibili.com")
}
使用Lumberjack进行日志切割归档
要使用第三方库Lumberjack实现
安装
go get gopkg.in/natefinch/lumberjack.v2
package main
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
"net/http"
)
var logger1 *zap.Logger
var sugarLogger *zap.SugaredLogger
// 初始化
func InitLogger1() {
//logger, _ = zap.NewProduction()
writeSyncer := getLogWriter()
encoder := getEncoder()
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
//zap.AddCaller()添加日志的调用信息,即哪个方法调用了日志
logger1 = zap.New(core, zap.AddCaller())
sugarLogger = logger1.Sugar()
}
func getEncoder() zapcore.Encoder {
encoderConfig := zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
FunctionKey: zapcore.OmitKey,
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
//将新生成的配置项传进去
return zapcore.NewConsoleEncoder(encoderConfig)
//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}
func getLogWriter() zapcore.WriteSyncer {
//file, _ := os.Create("./test.log")每次执行都会新建文件
//执行会往原有文件里面追加
//file, _ := os.OpenFile("./test.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0744)
//return zapcore.AddSync(file)
lumberJackLogger := &lumberjack.Logger{
//Filename: 日志文件的位置
//MaxSize:在进行切割之前,日志文件的最大大小(以MB为单位)
//MaxBackups:保留旧文件的最大个数
//MaxAges:保留旧文件的最大天数
//Compress:是否压缩/归档旧文件
Filename: "./test.log",
MaxSize: 10, //单位 MB
MaxBackups: 5,
MaxAge: 30,
Compress: false,
}
return zapcore.AddSync(lumberJackLogger)
}
// 模拟请求发送
func simpleHttpGet1(url string) {
resp, err := http.Get(url)
if err != nil {
//日志级别
sugarLogger.Error(
"Error fetching url",
zap.String("url", url),
zap.Error(err))
} else {
sugarLogger.Info("Success",
zap.String("statusCade", resp.Status),
zap.String("url", url))
resp.Body.Close()
}
}
func main() {
InitLogger1()
//在程序调用结束后,把日志记录到磁盘中
defer logger1.Sync()
simpleHttpGet1("www.bilibili.com")
simpleHttpGet1("https://www.bilibili.com")
}
gin框架配置zap记录日志
package main
import (
"net"
"net/http"
"net/http/httputil"
"os"
"runtime/debug"
"strings"
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
var logger2 *zap.Logger
var sugarLogger2 *zap.SugaredLogger
func main() {
InitLogger2()
//使用zap日志库处理gin框架日志
r := gin.New()
r.Use(GinLogger(logger2), GinRecovery(logger2, true))
r.GET("/hello", func(c *gin.Context) {
c.String(http.StatusOK, "hello qimi")
})
r.Run()
}
// 初始化
func InitLogger2() {
//logger, _ = zap.NewProduction()
writeSyncer := getLogWriter2()
encoder := getEncoder2()
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
//zap.AddCaller()添加日志的调用信息,即哪个方法调用了日志
logger2 = zap.New(core, zap.AddCaller())
sugarLogger2 = logger2.Sugar()
}
func getEncoder2() zapcore.Encoder {
encoderConfig := zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
FunctionKey: zapcore.OmitKey,
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
//将新生成的配置项传进去
return zapcore.NewConsoleEncoder(encoderConfig)
//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}
func getLogWriter2() zapcore.WriteSyncer {
//file, _ := os.Create("./test.log")每次执行都会新建文件
//执行会往原有文件里面追加
//file, _ := os.OpenFile("./test.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0744)
//return zapcore.AddSync(file)
lumberJackLogger := &lumberjack.Logger{
//Filename: 日志文件的位置
//MaxSize:在进行切割之前,日志文件的最大大小(以MB为单位)
//MaxBackups:保留旧文件的最大个数
//MaxAges:保留旧文件的最大天数
//Compress:是否压缩/归档旧文件
Filename: "./test.log",
MaxSize: 1, //单位 MB
MaxBackups: 5,
MaxAge: 30,
Compress: false,
}
return zapcore.AddSync(lumberJackLogger)
}
// 模拟请求发送
func simpleHttpGet2(url string) {
resp, err := http.Get(url)
if err != nil {
//日志级别
sugarLogger2.Error(
"Error fetching url",
zap.String("url", url),
zap.Error(err))
} else {
sugarLogger2.Info("Success",
zap.String("statusCade", resp.Status),
zap.String("url", url))
resp.Body.Close()
}
}
//===========================================================================================
// GinLogger 接收gin框架默认的日志
func GinLogger(logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
c.Next()
cost := time.Since(start)
logger.Info(path,
zap.Int("status", c.Writer.Status()),
zap.String("method", c.Request.Method),
zap.String("path", path),
zap.String("query", query),
zap.String("ip", c.ClientIP()),
zap.String("user-agent", c.Request.UserAgent()),
zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
zap.Duration("cost", cost),
)
}
}
// GinRecovery recover掉项目可能出现的panic
func GinRecovery(logger *zap.Logger, 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 {
logger.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 {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
zap.String("stack", string(debug.Stack())),
)
} else {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
}
c.AbortWithStatus(http.StatusInternalServerError)
}
}()
c.Next()
}
}
viper
Viper是适用于Go应用程序的完整配置解决方案。它被设计用于在应用程序中工作,并且可以处理所有类型的配置需求和格式。
安装
go get github.com/spf13/viper
viper的使用
package main
import (
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
)
func main() {
//设置默认值
viper.SetDefault("name", "./")
//读取配置文件
viper.SetConfigFile("./config.yaml") // 指定配置文件路径
viper.SetConfigName("config") // 配置文件名称(无扩展名)
viper.SetConfigType("yaml") // 如果配置文件的名称中没有扩展名,则需要配置此项
viper.AddConfigPath("/etc/appname/") // 查找配置文件所在的路径
viper.AddConfigPath("$HOME/.appname") // 多次调用以添加多个搜索路径
viper.AddConfigPath(".") // 还可以在工作目录中查找配置
err := viper.ReadInConfig() // 查找并读取配置文件
if err != nil { // 处理读取配置文件的错误
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
//在加载配置文件出错时,你可以像下面这样处理找不到配置文件的特定情况:
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// 配置文件未找到错误;如果需要可以忽略
} else {
// 配置文件被找到,但产生了另外的错误
}
}
// 配置文件找到并成功解析
//===================写入配置文件==================
viper.WriteConfig() // 将当前配置写入“viper.AddConfigPath()”和“viper.SetConfigName”设置的预定义路径
viper.SafeWriteConfig()
viper.WriteConfigAs("/path/to/my/.config")
viper.SafeWriteConfigAs("/path/to/my/.config") // 因为该配置文件写入过,所以会报错
viper.SafeWriteConfigAs("/path/to/my/.other_config")
//=====================监控并重新读取配置文件==========
//实时监控配置文件的变化
viper.WriteConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
//配置文件发生变更之后会调用的回调函数
fmt.Println("config file changed", e.Name)
})
}
优雅的关机或重启
优雅关机
优雅关机就是服务端关机命令发出后不是立即关机,而是等待当前还在处理的请求全部处理完毕后再退出程序,是一种对客户端友好的关机方式。而执行Ctrl+C关闭服务端时,会强制结束进程导致正在访问的请求出现问题。
在Go1.8版本之后,http.Server内置的Shutdown()方法就支持优雅的关机
代码实现:
package main
import (
"context"
"github.com/fvbock/endless"
"github.com/gin-gonic/gin"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
//==========================关机======================
r := gin.Default()
r.GET("/", func(c *gin.Context) {
time.Sleep(5 * time.Second)
c.String(http.StatusOK, "Welcome Gin Server")
})
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
go func() {
// 开启一个goroutine启动服务
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// 等待中断信号来优雅地关闭服务器,为关闭服务器操作设置一个5秒的超时
quit := make(chan os.Signal, 1) // 创建一个接收信号的通道
// kill 默认会发送 syscall.SIGTERM 信号
// kill -2 发送 syscall.SIGINT 信号,我们常用的Ctrl+C就是触发系统SIGINT信号
// kill -9 发送 syscall.SIGKILL 信号,但是不能被捕获,所以不需要添加它
// signal.Notify把收到的 syscall.SIGINT或syscall.SIGTERM 信号转发给quit
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) // 此处不会阻塞
<-quit // 阻塞在此,当接收到上述两种信号时才会往下执行
log.Println("Shutdown Server ...")
// 创建一个5秒超时的context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 5秒内优雅关闭服务(将未处理完的请求处理完再关闭服务),超过5秒就超时退出
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown: ", err)
}
log.Println("Server exiting")
//==========================重启===========================
r.GET("/", func(c *gin.Context) {
time.Sleep(5 * time.Second)
c.String(http.StatusOK, "hello gin!")
})
// 默认endless服务器会监听下列信号:
// syscall.SIGHUP,syscall.SIGUSR1,syscall.SIGUSR2,syscall.SIGINT,syscall.SIGTERM和syscall.SIGTSTP
// 接收到 SIGHUP 信号将触发`fork/restart` 实现优雅重启(kill -1 pid会发送SIGHUP信号)
// 接收到 syscall.SIGINT或syscall.SIGTERM 信号将触发优雅关机
// 接收到 SIGUSR2 信号将触发HammerTime
// SIGUSR1 和 SIGTSTP 被用来触发一些用户自定义的hook函数
if err := endless.ListenAndServe(":8080", r); err != nil {
log.Fatalf("listen: %s\n", err)
}
log.Println("Server exiting")
}
当你的项目是使用类似supervisor的软件管理进程时就不适用这种方式了。
GoWeb项目分层理念(CLD)


6609

被折叠的 条评论
为什么被折叠?



