zap日志库官网地址:https://github.com/uber-go/zap
zap的优点:性能高
Zap提供了两种类型的日志记录器—Sugared Logger和Logger
为什么logger的效率更高:因为logger指明了类型,zap就不会启用go的反射,这样效率就比Sugared Logger更高;但即使是Sugared Logger也比一般的日志库性能高很多了
SugaredLogger使用
package main
import (
"time"
"go.uber.org/zap"
)
func main() {
url := "https//www.baidu.com"
logger, _ := zap.NewProduction() // 生产环境下使用
// logger, _ := zap.NewDevelopment() // 开发环境下使用
defer logger.Sync() // flushes buffer, if any
sugar := logger.Sugar() // 使用sugar的实例,更方便的记录日志
sugar.Infow("failed to fetch URL",
// Structured context as loosely typed key-value pairs.
"url", url,
"attempt", 3,
"backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)
}
package main
import (
"go.uber.org/zap"
)
func main() {
url := "https//www.baidu.com"
logger, _ := zap.NewProduction() // 生产环境下使用
// logger, _ := zap.NewDevelopment() // 开发环境下使用
defer logger.Sync() // flushes buffer, if any
// 这种效率比较高,就是因为指明了类型,zap就不会启用go的反射,这样效率就比较高
logger.Info("failed to fetch URL",
zap.String("url", url),
zap.Int("nums", 3),
)
}
package main
import (
"time"
"go.uber.org/zap"
)
func NewLogger() (*zap.Logger, error) {
cfg := zap.NewProductionConfig()
cfg.OutputPaths = []string{ //可以定位到多个文件中
"./myproject.log",
"stderr",
"stdout",
}
return cfg.Build()
}
func main() {
logger, err := NewLogger()
if err != nil {
panic(err)
//panic("初始化logger失败")
}
su := logger.Sugar()
defer su.Sync()
url := "https://www.baidu.com"
su.Info("failed to fetch URL",
// Structured context as strongly typed Field values.
zap.String("url", url),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)
}
这里主要要做的两件事情:
1、初始化zap和初始化router分离出独立的模块
2、在模块router中实现与api的绑定
package initialize
import "go.uber.org/zap"
func InitLogger() {
logger, _ := zap.NewDevelopment()
zap.ReplaceGlobals(logger)
}
package initialize
import (
"web_api/user_web/router"
"github.com/gin-gonic/gin"
)
func Routers() *gin.Engine {
Router := gin.Default()
ApiGroup := Router.Group("v1")
router.InitUserRouter(ApiGroup)
return Router
}
package router
import (
"web_api/user_web/api"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
func InitUserRouter(Router *gin.RouterGroup) {
UserRouter := Router.Group("user")
zap.S().Info("配置用户相关的url")
{
UserRouter.GET("list", api.GetUserList)
}
}
package api
import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
func GetUserList(ctx *gin.Context) {
zap.S().Debug("获取用户列表")
}
package main
import (
"fmt"
"web_api/user_web/initialize"
"go.uber.org/zap"
)
func main() {
port := 8081
//2. 初始化logger
initialize.InitLogger()
//3. 初始化routers
Router := initialize.Routers()
/*
1. S()可以获取一个全局的sugar,可以让我们自己设置一个全局的logger
2. 日志是分级别的,debug, info , warn, error, fetal
debug最低,fetal最高,如果配置成info,所有比info低的都不会输出
NewProduction默认日志级别为info
NewDevelopment默认日志级别为debug
3. S函数和L函数很有用, 提供了一个全局的安全访问logger的途径
*/
zap.S().Debugf("启动服务器, 端口: %d", port)
if err := Router.Run(fmt.Sprintf(":%d", port)); err != nil {
zap.S().Panic("启动失败:", err.Error())
}
}
ApiGroup := Router.Group("/u/v1")
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative *.proto
syntax = "proto3";
import "google/protobuf/empty.proto";
option go_package = ".;proto";
service User{
rpc GetUserList(PageInfo) returns (UserListResponse); // 用户列表
rpc GetUserByMobile(MobileRequest) returns (UserInfoResponse); //通过mobile查询用户
rpc GetUserById(IdRequest) returns (UserInfoResponse); //通过id查询用户
rpc CreateUser(CreateUserInfo) returns (UserInfoResponse); // 添加用户
rpc UpdateUser(UpdateUserInfo) returns (google.protobuf.Empty); // 更新用户
rpc CheckPassWord(PasswordCheckInfo) returns (CheckResponse); //检查密码
}
message PageInfo {
uint32 pn = 1;
uint32 pSize = 2;
}
message UserInfoResponse {
int32 id = 1;
string passWord = 2;
string mobile = 3;
string nickName = 4;
uint64 birthDay = 5;
string gender = 6;
int32 role = 7;
}
message UserListResponse {
int32 total = 1;
repeated UserInfoResponse data = 2;
}
message CreateUserInfo {
string nickName = 1;
string passWord = 2;
string mobile = 3;
}
message MobileRequest{
string mobile = 1;
}
message IdRequest {
int32 id = 1;
}
message UpdateUserInfo {
int32 id = 1;
string nickName = 2;
string gender = 3;
uint64 birthDay = 4;
}
message PasswordCheckInfo {
string password = 1;
string encryptedPassword = 2;
}
message CheckResponse{
bool success = 1;
}
package response
import (
"fmt"
"time"
)
type JsonTime time.Time
// 内部自动调用MarshalJSON方法
func (j JsonTime) MarshalJSON() ([]byte, error) {
var stmp = fmt.Sprintf("\"%s\"", time.Time(j).Format("2006-01-02"))
return []byte(stmp), nil
}
type UserResponse struct {
Id int32 `json:"id"`
NickName string `json:"name"`
Birthday JsonTime `json:"birthday"`
Gender string `json:"gender"`
Mobile string `json:"mobile"`
}
package api
import (
"context"
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
"web_api/user_web/global/response"
"web_api/user_web/proto"
)
func HandleGrpcErrorToHttp(err error, c *gin.Context) {
//将grpc的code转换成http的状态码
if err != nil {
if e, ok := status.FromError(err); ok {
switch e.Code() {
case codes.NotFound:
c.JSON(http.StatusNotFound, gin.H{
"msg": e.Message(),
})
case codes.Internal:
c.JSON(http.StatusInternalServerError, gin.H{
"msg:": "内部错误",
})
case codes.InvalidArgument:
c.JSON(http.StatusBadRequest, gin.H{
"msg": "参数错误",
})
case codes.Unavailable:
c.JSON(http.StatusInternalServerError, gin.H{
"msg": "用户服务不可用",
})
default:
c.JSON(http.StatusInternalServerError, gin.H{
"msg": e.Code(),
})
}
return
}
}
}
func GetUserList(ctx *gin.Context) {
ip := "127.0.0.1"
port := 50051
//拨号连接用户grpc服务器
userConn, err := grpc.Dial(fmt.Sprintf("%s:%d", ip, port), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
zap.S().Errorw("[GetUserList] 连接 【用户服务失败】", "msg", err.Error())
}
//生成grpc的client并调用接口
userSrvClient := proto.NewUserClient(userConn)
rsp, err := userSrvClient.GetUserList(context.Background(), &proto.PageInfo{
Pn: 0,
PSize: 0,
})
if err != nil {
zap.S().Errorw("[GetUserList] 查询 【用户列表】 失败")
HandleGrpcErrorToHttp(err, ctx)
return
}
result := make([]interface{}, 0)
for _, value := range rsp.Data {
user := response.UserResponse{
Id: value.Id,
NickName: value.NickName,
//Birthday: time.Time(time.Unix(int64(value.BirthDay), 0)).Format("2006-01-02"),
Birthday: response.JsonTime(time.Unix(int64(value.BirthDay), 0)),
Gender: value.Gender,
Mobile: value.Mobile,
}
result = append(result, user)
}
ctx.JSON(http.StatusOK, result)
}
name: 'user-web'
mysql:
host: '127.0.0.1'
port: 3306
package main
import (
"fmt"
"github.com/spf13/viper"
)
type ServerConfig struct {
ServiceName string `mapstructure:"name"`
Port int `mapstructure:"port"`
}
func main() {
v := viper.New()
//文件的路径如何设置
v.SetConfigFile("viper_test/config.yaml")
if err := v.ReadInConfig(); err != nil {
panic(err)
}
serverConfig := ServerConfig{}
if err := v.Unmarshal(&serverConfig); err != nil {
panic(err)
}
fmt.Println(serverConfig)
fmt.Printf("%v", v.Get("name"))
}
fsnotify
文件变化通知库来实现动态监控package main
import (
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"time"
)
//如何将线上和线下的配置文件隔离
//不用改任何代码而且线上和线上的配置文件能隔离开
type MysqlConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
}
type ServerConfig struct {
ServiceName string `mapstructure:"name"`
MysqlInfo MysqlConfig `mapstructure:"mysql"`
}
func GetEnvInfo(env string) bool {
viper.AutomaticEnv()
return viper.GetBool(env)
//刚才设置的环境变量 想要生效 我们必须得重启goland
}
func main() {
debug := GetEnvInfo("DEV_CONFIG")
configFilePrefix := "config"
configFileName := fmt.Sprintf("viper_test/%s_pro.yaml", configFilePrefix)
if debug {
configFileName = fmt.Sprintf("viper_test/%s_debug.yaml", configFilePrefix)
}
v := viper.New()
//文件的路径如何设置
v.SetConfigFile(configFileName)
if err := v.ReadInConfig(); err != nil {
panic(err)
}
serverConfig := ServerConfig{}
if err := v.Unmarshal(&serverConfig); err != nil {
panic(err)
}
fmt.Println(serverConfig)
fmt.Printf("%v", v.Get("name"))
//viper的功能 - 动态监控变化
v.WatchConfig()
v.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("config file channed: ", e.Name)
_ = v.ReadInConfig()
_ = v.Unmarshal(&serverConfig)
fmt.Println(serverConfig)
})
time.Sleep(time.Second * 300)
}
//config_pro.yaml
name: 'user-web'
mysql:
host: '127.0.0.2'
port: 3309
//config_debug.yaml
name: 'user-web2'
mysql:
host: '127.0.0.1'
port: 3306
//config_debug.yaml
name: 'user-web'
port: '8081'
user_srv:
host: '127.0.0.1'
port: '50051'
//config_pro.yaml
name: 'user-web'
port: '8031'
user_srv:
host: '127.0.0.1'
port: '50052'
package config
type UserSrvConfig struct {
Host string `mapstructure:"host" json:"host"`
Port int `mapstructure:"port" json:"port"`
}
type ServerConfig struct {
Name string `mapstructure:"name" json:"name"`
Port int `mapstructure:"port" json:"port"`
UserSrvInfo UserSrvConfig `mapstructure:"user_srv" json:"user_srv"`
}
package global
import "web_api/user_web/config"
var (
ServerConfig *config.ServerConfig = &config.ServerConfig{}
)
package initialize
import (
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"go.uber.org/zap"
"web_api/user_web/global"
)
func GetEnvInfo(env string) bool {
viper.AutomaticEnv()
return viper.GetBool(env)
//刚才设置的环境变量 想要生效 我们必须得重启goland
}
func InitConfig() {
debug := GetEnvInfo("DEV_CONFIG")
configFilePrefix := "config"
configFileName := fmt.Sprintf("%s_pro.yaml", configFilePrefix)
if debug {
configFileName = fmt.Sprintf("%s_debug.yaml", configFilePrefix)
}
v := viper.New()
//文件的路径如何设置
v.SetConfigFile(configFileName)
if err := v.ReadInConfig(); err != nil {
panic(err)
}
//这个对象如何在其他文件中使用 - 全局变量
if err := v.Unmarshal(&global.ServerConfig); err != nil {
panic(err)
}
zap.S().Infof("配置信息: &v", global.ServerConfig)
//viper的功能 - 动态监控变化
v.WatchConfig()
v.OnConfigChange(func(e fsnotify.Event) {
zap.S().Infof("配置文件产生变化: &s", e.Name)
_ = v.ReadInConfig()
_ = v.Unmarshal(&global.ServerConfig)
zap.S().Infof("配置信息: &v", global.ServerConfig)
})
}
initialize.InitConfig()
global.ServerConfig.Port
package main
import (
"fmt"
"web_api/user_web/global"
"web_api/user_web/initialize"
"go.uber.org/zap"
)
func main() {
//1. 初始化logger
initialize.InitLogger()
//2. 初始化配置文件
initialize.InitConfig()
//3. 初始化routers
Router := initialize.Routers()
/*
1. S()可以获取一个全局的sugar,可以让我们自己设置一个全局的logger
2. 日志是分级别的,debug, info , warn, error, fetal
debug最低,fetal最高,如果配置成info,所有比info低的都不会输出
NewProduction默认日志级别为info
NewDevelopment默认日志级别为debug
3. S函数和L函数很有用, 提供了一个全局的安全访问logger的途径
*/
zap.S().Debugf("启动服务器, 端口: %d", global.ServerConfig.Port)
if err := Router.Run(fmt.Sprintf(":%d", global.ServerConfig.Port)); err != nil {
zap.S().Panic("启动失败:", err.Error())
}
}