复盘orm库gorm框架

gorm是一个使用Go语言编写的ORM框架。它文档齐全,对开发者友好,支持主流数据库。
基础配置

type DbConfig struct {
    DbHost        string `mapstructure:"db_host" json:"db_host" yaml:"db_host"`
    DbPort        int    `mapstructure:"db_port" json:"db_port" yaml:"db_port"`
    DbName        string `mapstructure:"db_name" json:"db_name" yaml:"db_name"`
    DbUser        string `mapstructure:"db_user" json:"db_user" yaml:"db_user"`
    DbPass        string `mapstructure:"db_pass" json:"db_pass" yaml:"db_pass"`
    TablePrefix   string `mapstructure:"table_prefix" json:"table_prefix" yaml:"table_prefix"`
    TimeZone      string `mapstructure:"time_zone" json:"time_zone" yaml:"time_zone"`
    LogLevel      int    `mapstructure:"log_level" json:"log_level" yaml:"log_level"`                // LogLevel SQL日志级别 (1-静音 2-错误 3-警告 4-信息)
    SlowThreshold int    `mapstructure:"slow_threshold" json:"slow_threshold" yaml:"slow_threshold"` // SlowThreshold 慢SQL阈值(毫秒)。慢SQL会在log_level大于等于3时输出。
    IdleConns     int    `mapstructure:"idle_conns" json:"idle_conns" yaml:"idle_conns"`             // 空闲连接池中的最大连接数,建议为open_conns的百分之5-20之间
    OpenConns     int    `mapstructure:"open_conns" json:"open_conns" yaml:"open_conns"`             // 最大打开连接数,建议这里设置为50
}

func InitGorm(conf *config.Config) *gorm.DB {
    dsn := fmt.Sprintf("host=%s port=%d dbname=%s user=%s password=%s sslmode=disable TimeZone=%s",
        conf.Db.DbHost, conf.Db.DbPort, conf.Db.DbName, conf.Db.DbUser, conf.Db.DbPass, conf.Db.TimeZone)
    postgresConfig := postgres.Config{
        DSN:                  dsn,  // DSN data source name
        PreferSimpleProtocol: true, // 禁用隐式 prepared statement
    }

    // 使用标准日志库的New方法创建日志输出
    //newLogger := logger.New(
    //  log.New(os.Stdout, "\r\n", log.LstdFlags),
    //  logger.Config{
    //      SlowThreshold:             time.Duration(dbConf.SlowThreshold) * time.Millisecond, // 慢SQL阈值
    //      LogLevel:                  logger.LogLevel(dbConf.LogLevel),                       // 日志级别
    //      IgnoreRecordNotFoundError: true,
    //      Colorful:                  true,
    //  })

    // zap 自定义
    newLogger := NewGormLogger(x_logger.GormZapLog.GetZapLogger(), logger.LogLevel(conf.Db.LogLevel))

    PGDB, err := gorm.Open(postgres.New(postgresConfig), &gorm.Config{
        //Logger: newLogger,
        //Logger: logger.Default.LogMode(logger.Info),
        Logger: newLogger,
        NamingStrategy: schema.NamingStrategy{
            SingularTable: false, // 使用单数表名,在启用此选项的情况下,“user”的表将是“user”
            TablePrefix:   "te_", // 表前缀
        },
    })

    DB := PGDB

    if err != nil {
        fmt.Println("数据库链接失败")
        os.Exit(0)
    }
    AutoMigrate(DB)
    sqlDB, _ := DB.DB()
    sqlDB.SetMaxIdleConns(conf.Db.IdleConns)
    sqlDB.SetMaxOpenConns(conf.Db.OpenConns)
    // 设置数据库连接池中连接的最大生命周期
    sqlDB.SetConnMaxLifetime(time.Hour)
    fmt.Println("[+]PG连接成功!")
    return DB
}

// AutoMigrate 自动迁移
func AutoMigrate(db *gorm.DB) {
    err := db.AutoMigrate(
        new(model.User),
    )
    if err != nil {
        fmt.Println("[-] 迁移数据表失败:", err.Error())
        os.Exit(0)
    }
}

基础的数据操作

    // 获取表名
    m := &model.User{}
    modelType := reflect.TypeOf(m).Elem()
    fmt.Println(modelType)
    tableName := global.Db.NamingStrategy.TableName("User")
    fmt.Println(tableName)
    //
    // 插入一条记录
    user := model.User{Name: "刘建超"}
    result := global.Db.Create(&user) // 在数据库中插入一条新记录
    fmt.Println(result.Error)         // 如果有错误,打印错误
    fmt.Println(user.ID)              // 打印插入后生成的 ID
    fmt.Printf("%#v", user)           // 打印插入后生成的 ID

    // Save 修改记录
    user = model.User{Name: "刘建超iuu"}
    user.ID = 1
    // 排除指定字段
    global.Db.Omit("Created").Save(&user)

    // 软删除
    global.Db.Delete(&user)

    // 如果主键为空,插入新记录
    user = model.User{Name: "iuu"}
    global.Db.Save(&user)

    user = model.User{Name: "刘建超iuuxxx"}
    user.ID = 2
    global.Db.Select("Name").Save(&user)

    //永久删除
    global.Db.Unscoped().Delete(&user)

Docker安装PostgreSQL

Docker安装PostgreSQL

拉取镜像

docker pull postgres:latest

用于生成默认的配置文件

docker run -i --rm postgres cat /usr/share/postgresql/postgresql.conf.sample > my-postgres.conf

启动容器

docker run -d \
-p 15432:5432 \
--restart=always \
--name docker-postgres \
--privileged=true \
-e TZ=Asia/Shanghai \
-e PGTZ=Asia/Shanghai \
-e POSTGRES_USER=iuu_postgres \
-e POSTGRES_PASSWORD=iuu_postgres \
-v /home/Software/Docker/postgres/postgres.conf:/etc/postgresql/postgresql.conf \
-v /home/Software/Docker/postgres/data:/var/lib/postgresql/data \
postgres:latest \
-c 'config_file=/etc/postgresql/postgresql.conf'

进入容器

docker exec -it docker-postgres  /bin/bash
# 直接进入pgsql
docker exec -it docker-postgres psql -U iuu_postgres

查看所有数据库

\list

查看所有数据库

SELECT datname FROM pg_database;

查看当前时区

show time zone; 或者 show timezone;

查看数据库的时区与时间:

SELECT now();

查看数据库可供选择的时区:

select * from pg_timezone_names;

设置时区:

set time zone "Asia/Shanghai";

但是通过这种方式设置时区在你退出psql终端后,再次进入此psql中断后就会发现又恢复到原来的时区了,如果想永久修改,我们需要更改配置文件:

log_timezone = 'PRC'
timezone = 'PRC'
# 'PRC' 改为 'Asia/Shanghai'

复盘配置库viper框架

Viper是适用于Go应用程序的完整配置解决方案。它被设计用于在应用程序中工作,并且可以处理所有类型的配置需求和格式。
main函数基本配置如下

package main

import "vipertest/core"

func main() {
    //core.InitYamlViper("./yaml.yaml")
    //core.InitTomlViper("./toml.toml")
    //core.InitJsonViper("./json.json")
    core.InitIniViper("./ini.ini")

    select {}
}

程序根目录创建几个格式的配置文件
ini格式

[database]
server=127.0.0.1
ports=8003,8004,8007

[servers.alpha]
ip=127.0.0.1

json格式

{
  "database": {
    "server": "127.0.0.1",
    "ports": [8003, 8004, 8007]
  },
  "servers": {
    "alpha": {
      "ip": "127.0.0.1"
    }
  }
}

yaml格式

database:
  server: 127.0.0.1
  ports:
    - 8003
    - 8004
    - 8007

servers:
  alpha:
    ip: 127.0.0.1

toml格式

[database]
server = "127.0.0.1"
ports = [ 8004, 8005, 8006 ]

[servers]
[servers.alpha]
ip = "127.0.0.1"

核心代码如下

package core

import (
    "fmt"
    "github.com/fsnotify/fsnotify"
    "github.com/spf13/viper"
)

type DatabaseConfig struct {
    Server string `mapstructure:"server" json:"server" yaml:"server"`
    Ports  []int  `mapstructure:"ports" json:"ports" yaml:"ports"`
}

type ServersConfig struct {
    Alpha ServerInfo `mapstructure:"alpha" json:"alpha" yaml:"alpha"`
}

type ServerInfo struct {
    IP string `mapstructure:"ip" json:"ip" yaml:"ip"`
}

type Ini struct {
    Database DatabaseConfig `json:"database" ini:"database"`
    Servers  ServersConfig  `json:"servers" ini:"servers"`
}

var IniConf Ini

type Json struct {
    Database DatabaseConfig `json:"database"`
    Servers  ServersConfig  `json:"servers"`
}

var JsonConf Json

type Toml struct {
    Database DatabaseConfig `toml:"database"`
    Servers  ServersConfig  `toml:"servers"`
}

var TomlConf Toml

type Yaml struct {
    Database DatabaseConfig `yaml:"database"`
    Servers  ServersConfig  `yaml:"servers"`
}

var YamlConf Yaml

// InitYamlViper 解析yaml格式文件
func InitYamlViper(path string) {
    v := viper.New()
    // 指定配置文件路径
    v.SetConfigFile(path)
    // 如果配置文件的名称中没有扩展名,则需要配置此项
    v.SetConfigType("yaml")
    // 查找并读取配置文件
    err := v.ReadInConfig()
    if err != nil {
        fmt.Printf("读取配置文件错误: %s \n", err)
    }
    // 监控并重新读取配置文件
    v.WatchConfig()
    v.OnConfigChange(func(e fsnotify.Event) {
        fmt.Println("配置文件内容已更改:", e.Name)
        fmt.Println("all settings: ", v.AllSettings())
        fmt.Println("database.server: ", v.GetString("database.server"))
        fmt.Println("database.ports: ", v.GetIntSlice("database.ports"))
        fmt.Println("--------------")
        fmt.Println("servers.alpha.ip: ", v.GetString("servers.alpha.ip"))

        if err = v.Unmarshal(&YamlConf); err != nil {
            fmt.Printf("重新解析配置文件失败: %s \n", err)
        }

        fmt.Printf("新配置为:%#v\n", YamlConf)
        fmt.Printf("新配置database.ports: %#v\n", YamlConf.Database.Ports)

    })

    fmt.Println("all settings: ", v.AllSettings())
    fmt.Println("database.server: ", v.GetString("database.server"))
    fmt.Println("database.ports: ", v.GetIntSlice("database.ports"))
    fmt.Println("--------------")
    fmt.Println("servers.alpha.ip: ", v.GetString("servers.alpha.ip"))
    if err = v.Unmarshal(&YamlConf); err != nil {
        fmt.Printf("解析配置文件失败: %s \n", err)
    }
    fmt.Printf("配置为:%#v\n", YamlConf)
    fmt.Printf("database.ports: %#v\n", YamlConf.Database.Ports)

}

// InitTomlViper 解析toml格式文件
func InitTomlViper(path string) {
    v := viper.New()
    // 指定配置文件路径
    v.SetConfigFile(path)
    // 如果配置文件的名称中没有扩展名,则需要配置此项
    v.SetConfigType("toml")
    // 查找并读取配置文件
    err := v.ReadInConfig()
    if err != nil {
        fmt.Printf("读取配置文件错误: %s \n", err)
    }
    // 监控并重新读取配置文件
    v.WatchConfig()
    v.OnConfigChange(func(e fsnotify.Event) {
        fmt.Println("配置文件内容已更改:", e.Name)
        fmt.Println("all settings: ", v.AllSettings())
        fmt.Println("database.server: ", v.GetString("database.server"))
        fmt.Println("database.ports: ", v.GetIntSlice("database.ports"))
        fmt.Println("--------------")
        fmt.Println("servers.alpha.ip: ", v.GetString("servers.alpha.ip"))

        if err = v.Unmarshal(&TomlConf); err != nil {
            fmt.Printf("重新解析配置文件失败: %s \n", err)
        }
        fmt.Printf("新配置为:%#v\n", TomlConf)
        fmt.Printf("新配置database.ports: %#v\n", TomlConf.Database.Ports)

    })
    fmt.Println("all settings: ", v.AllSettings())
    fmt.Println("database.server: ", v.GetString("database.server"))
    fmt.Println("database.ports: ", v.GetIntSlice("database.ports"))
    fmt.Println("--------------")
    fmt.Println("servers.alpha.ip: ", v.GetString("servers.alpha.ip"))
    if err = v.Unmarshal(&TomlConf); err != nil {
        fmt.Printf("解析配置文件失败: %s \n", err)
    }
    fmt.Printf("配置为:%#v\n", TomlConf)
    fmt.Printf("database.ports: %#v\n", TomlConf.Database.Ports)
}

// InitJsonViper 解析json格式文件
func InitJsonViper(path string) {
    v := viper.New()
    // 指定配置文件路径
    v.SetConfigFile(path)
    // 如果配置文件的名称中没有扩展名,则需要配置此项
    v.SetConfigType("json")
    // 查找并读取配置文件
    err := v.ReadInConfig()
    if err != nil {
        fmt.Printf("读取配置文件错误: %s \n", err)
    }
    // 监控并重新读取配置文件
    v.WatchConfig()
    v.OnConfigChange(func(e fsnotify.Event) {
        fmt.Println("配置文件内容已更改:", e.Name)
        fmt.Println("all settings: ", v.AllSettings())
        fmt.Println("database.server: ", v.GetString("database.server"))
        fmt.Println("database.ports: ", v.GetIntSlice("database.ports"))
        fmt.Println("--------------")
        fmt.Println("servers.alpha.ip: ", v.GetString("servers.alpha.ip"))

        if err = v.Unmarshal(&JsonConf); err != nil {
            fmt.Printf("重新解析配置文件失败: %s \n", err)
        }
        fmt.Printf("新配置为:%#v\n", JsonConf)
        fmt.Printf("新配置database.ports: %#v\n", JsonConf.Database.Ports)

    })
    fmt.Println("all settings: ", v.AllSettings())
    fmt.Println("database.server: ", v.GetString("database.server"))
    fmt.Println("database.ports: ", v.GetIntSlice("database.ports"))
    fmt.Println("--------------")
    fmt.Println("servers.alpha.ip: ", v.GetString("servers.alpha.ip"))
    if err = v.Unmarshal(&JsonConf); err != nil {
        fmt.Printf("解析配置文件失败: %s \n", err)
    }
    fmt.Printf("配置为:%#v\n", JsonConf)
    fmt.Printf("database.ports: %#v\n", JsonConf.Database.Ports)
}

// InitIniViper 解析ini格式文件
func InitIniViper(path string) {
    v := viper.New()
    // 指定配置文件路径
    v.SetConfigFile(path)
    // 如果配置文件的名称中没有扩展名,则需要配置此项
    v.SetConfigType("ini")
    // 查找并读取配置文件
    err := v.ReadInConfig()
    if err != nil {
        fmt.Printf("读取配置文件错误: %s \n", err)
    }
    // 监控并重新读取配置文件
    v.WatchConfig()
    v.OnConfigChange(func(e fsnotify.Event) {
        fmt.Println("配置文件内容已更改:", e.Name)
        fmt.Println("all settings: ", v.AllSettings())
        fmt.Println("database.server: ", v.GetString("database.server"))
        fmt.Println("database.ports: ", v.GetStringSlice("database.ports"))
        fmt.Println("--------------")
        fmt.Println("servers.alpha.ip: ", v.GetString("servers.alpha.ip"))

        if err = v.Unmarshal(&IniConf); err != nil {
            fmt.Printf("重新解析配置文件失败: %s \n", err)
        }
        fmt.Printf("新配置为:%#v\n", IniConf)
        fmt.Printf("新配置database.ports: %#v\n", IniConf.Database.Ports)

    })
    fmt.Println("all settings: ", v.AllSettings())
    fmt.Println("database.server: ", v.GetString("database.server"))
    fmt.Println("database.ports: ", v.GetStringSlice("database.ports"))
    fmt.Println("--------------")
    fmt.Println("servers.alpha.ip: ", v.GetString("servers.alpha.ip"))
    if err = v.Unmarshal(&IniConf); err != nil {
        fmt.Printf("解析配置文件失败: %s \n", err)
    }
    fmt.Printf("配置为:%#v\n", IniConf)
    fmt.Printf("database.ports: %#v\n", IniConf.Database.Ports)
}

复盘日志库logrus框架

它是一个结构化、插件化的日志记录库。完全兼容 golang 标准库中的日志模块。它还内置了 2 种日志输出格式 JSONFormatter 和 TextFormatter,来定义输出的日志格式。
main函数基本配置如下

package main

import "logrustest/core"

// go get -u github.com/natefinch/lumberjack@v2
func main() {

    //fmt.Println(path.Join("logs", "app-%Y-%m-%d.log"))
    core.InitLogrus()
    core.Log.Println("Listening and serving HTTP on", 1, ":", 2)
    for i := 0; i < 10000000; i++ {
        core.Log.Info("日志内容:", 1212121) // 写入文件
    }

}

核心配置如下

package core

import (
    "fmt"
    rotatelogs "github.com/lestrrat-go/file-rotatelogs"
    "github.com/rifflock/lfshook"
    "github.com/sirupsen/logrus"
    "gopkg.in/natefinch/lumberjack.v2"
    "io"
    "logrustest/utils"
    "os"
    "path"
    "path/filepath"
    "time"
)

// Log 实例
var Log *logrus.Logger

// customFormatter 自定义日志
type customFormatter struct {
    logrus.TextFormatter
}

func (f *customFormatter) Format(entry *logrus.Entry) ([]byte, error) {
    var levelColor string
    var levelText string
    switch entry.Level {
    case logrus.DebugLevel:
        levelColor, levelText = "34", "DEBUG" // 蓝色
    case logrus.InfoLevel:
        levelColor, levelText = "36", "INFO " // 青色
    case logrus.WarnLevel:
        levelColor, levelText = "33", "WARN " // 黄色
    case logrus.ErrorLevel:
        levelColor, levelText = "31", "ERROR" // 红色
    case logrus.FatalLevel, logrus.PanicLevel:
        levelColor, levelText = "31", "FATAL" // 红色,更重要的错误
    default:
        levelColor, levelText = "0", "UNKNOWN" // 默认颜色
    }

    // 获取调用者信息
    var fileAndLine string
    if entry.HasCaller() {
        dir := filepath.Dir(entry.Caller.File)
        fileAndLine = fmt.Sprintf("%s/%s:%d", filepath.Base(dir), filepath.Base(entry.Caller.File), entry.Caller.Line)
    }

    // 组装格式化字符串
    msg := fmt.Sprintf("\033[1;%sm%s\033[0m \033[4;1;%sm[%s]\033[0m \033[1;%sm[%s]\033[0m %s\n",
        levelColor, levelText, // 日志级别,带颜色
        levelColor, entry.Time.Format("2006-01-02 15:04:05.9999"), // 时间戳,下划线加颜色
        levelColor, fileAndLine, // 文件名:行号,带颜色
        entry.Message, // 日志消息
    )
    return []byte(msg), nil
}

func InitLogrus() error {
    Log = logrus.New()
    // 设置在输出日志中添加文件名和方法信息:
    Log.SetReportCaller(true)
    // 设置日志格式 自定义
    //Log.SetFormatter(&customFormatter{logrus.TextFormatter{
    //  ForceColors:   true, // 设置日志格式为带颜色
    //  FullTimestamp: true, // 完整时间戳的文本格式
    //}})

    // 设置日志格式 json
    //Log.SetFormatter(&logrus.JSONFormatter{
    //  TimestampFormat: "2006-01-02 15:04:05", // 设置json里的日期输出格式
    //})

    // 设置日志格式 text
    Log.SetFormatter(&logrus.TextFormatter{
        TimestampFormat:           "2006-01-02 15:04:05",
        ForceColors:               true,
        EnvironmentOverrideColors: false,
        FullTimestamp:             true,
        DisableLevelTruncation:    true,
    })

    var logLevels = map[string]logrus.Level{
        "panic": logrus.PanicLevel,
        "fatal": logrus.FatalLevel,
        "error": logrus.ErrorLevel,
        "warn":  logrus.WarnLevel,
        "info":  logrus.InfoLevel,
        "debug": logrus.DebugLevel,
        "trace": logrus.TraceLevel,
    }

    // 加载配置中的 日志级别
    levelStr := "debug"
    if level, ok := logLevels[levelStr]; ok {
        // 设置输出警告级别
        Log.SetLevel(level)
    } else {
        Log.Error("Invalid log level in config, setting to default level")
        // 设置默认级别
        Log.SetLevel(logrus.InfoLevel)
    }

    // 判断是否有存储日志文件夹
    // logs 目录
    logPath := "logs"
    if ok, _ := utils.PathExists(logPath); !ok {
        // 不存在创建日志存储目录
        fmt.Printf("create %v directory\n", logPath)
        _ = os.Mkdir(logPath, os.ModePerm)
    }

    // 按照大小分割日志
    //logger := &lumberjack.Logger{
    //  Filename:   path + "/app.log",
    //  MaxSize:    1,    // 日志文件大小,单位是 MB
    //  MaxBackups: 3,    // 最大过期日志保留个数
    //  MaxAge:     28,   // 保留过期文件最大时间,单位 天
    //  Compress:   true, // 是否压缩日志,默认是不压缩。这里设置为true,压缩日志
    //}
    // logrus 设置日志的输出方式
    //Log.SetOutput(logger)

    // 按照日期分割日志
    //linkName := "latest_log"
    //fileWriter, err := rotatelogs.New(
    //  path.Join(logPath, "%Y-%m-%d-%H-%M.log"),
    //  rotatelogs.WithMaxAge(7*24*time.Hour),      // 日志最大保存时间
    //  rotatelogs.WithLinkName(linkName),          //生成软链,指向最新日志文件
    //  rotatelogs.WithRotationTime(time.Minute),   //最小为1分钟轮询。默认60s  低于1分钟就按1分钟来
    //  // rotatelogs.WithRotationCount(3),            //设置3份 大于3份 或到了清理时间 开始清理
    //  rotatelogs.WithRotationSize(100*1024*1024), //设置1MB大小,当大于这个容量时,创建新的日志文件
    //)
    //if err != nil {
    //  return err
    //}
    // logrus 设置日志的输出方式
    //Log.SetOutput(fileWriter)

    // 自定义勾子写法 按照大小分割
    //logFile := &lumberjack.Logger{
    //  Filename:   logPath + "/app.log",
    //  MaxSize:    1,     // 日志文件大小,单位是 MB
    //  MaxBackups: 5,     // 最大过期日志保留个数
    //  MaxAge:     28,    // 保留过期文件最大时间,单位 天
    //  Compress:   false, // 是否压缩日志,默认是不压缩。这里设置为true,压缩日志
    //}
    //Log.AddHook(&lumberjackHook{
    //  logger: logFile,
    //})
    ////logrus 设置日志的输出丢弃
    //Log.SetOutput(io.Discard)

    // 勾子写法 按照时间分割
    //fileSplitWriter := NewLfsFileDateHook("", 0)
    //Log.AddHook(fileSplitWriter)
    //Log.SetOutput(io.Discard)

    // 勾子写法 按照日志大小分割
    fileSplitWriter := NewLfsFileSizeHook("", 0)
    Log.AddHook(fileSplitWriter)
    Log.SetOutput(io.Discard) // 如果设置该选项则为终端不打印

    //Log.Println("Logrus设置完成...")
    return nil
}

// hook的方式写入文件 大小
type lumberjackHook struct {
    logger *lumberjack.Logger
}

// Levels 返回该钩子应用的日志级别
func (hook *lumberjackHook) Levels() []logrus.Level {
    return logrus.AllLevels
}

// Fire 处理每一条日志记录,并将日志写入 lumberjack 管理的文件
func (hook *lumberjackHook) Fire(entry *logrus.Entry) error {
    // 格式化日志内容
    line, err := entry.String()
    if err != nil {
        return err
    }
    // 将日志写入文件
    _, err = hook.logger.Write([]byte(line))
    return err
}

// NewLfsFileSizeHook hook的方式写入文件 大小
func NewLfsFileSizeHook(logName string, maxRemainCnt uint) logrus.Hook {
    logPath := "logs"
    writer := &lumberjack.Logger{
        Filename:   path.Join(logPath, "app.log"),
        MaxSize:    1,    // 日志文件大小,单位是 MB
        MaxBackups: 3,    // 最大过期日志保留个数
        MaxAge:     28,   // 保留过期文件最大时间,单位 天
        Compress:   true, // 是否压缩日志,默认是不压缩。这里设置为true,压缩日志
    }

    logrus.SetLevel(logrus.InfoLevel)
    lfsHook := lfshook.NewHook(lfshook.WriterMap{
        logrus.DebugLevel: writer,
        logrus.InfoLevel:  writer,
        logrus.WarnLevel:  writer,
        logrus.ErrorLevel: writer,
        logrus.FatalLevel: writer,
        logrus.PanicLevel: writer,
    }, &logrus.TextFormatter{DisableColors: true})

    return lfsHook
}

// NewLfsFileDateHook hook的方式写入文件 日期
func NewLfsFileDateHook(logName string, maxRemainCnt uint) logrus.Hook {
    logPath := "logs"
    linkName := "latest_log"
    writer, err := rotatelogs.New(
        path.Join(logPath, "app-%Y-%m-%d.log"),
        rotatelogs.WithMaxAge(7*24*time.Hour),    // 日志最大保存时间
        rotatelogs.WithLinkName(linkName),        //生成软链,指向最新日志文件
        rotatelogs.WithRotationTime(time.Minute), //最小为1分钟轮询。默认60s  低于1分钟就按1分钟来
        //rotatelogs.WithRotationCount(5),            //设置3份 大于3份 或到了清理时间 开始清理
        rotatelogs.WithRotationSize(100*1024*1024), //设置1MB大小,当大于这个容量时,创建新的日志文件
    )
    if err != nil {
        logrus.Panicf("日志文件配置错误,无法记录日志: %v", err)
    }
    logrus.SetLevel(logrus.InfoLevel)
    lfsHook := lfshook.NewHook(lfshook.WriterMap{
        logrus.DebugLevel: writer,
        logrus.InfoLevel:  writer,
        logrus.WarnLevel:  writer,
        logrus.ErrorLevel: writer,
        logrus.FatalLevel: writer,
        logrus.PanicLevel: writer,
    }, &logrus.TextFormatter{DisableColors: true})

    return lfsHook
}

复盘日志库zap框架

zap 是 uber 开源的一个高性能,结构化,分级记录的日志记录包。
main函数基本配置如下

core.Config = &core.ZapConf{
    Level:         "info",                       // 级别
    Format:        "console",                    // 输出 console json
    Prefix:        "[server]",                   // 前缀
    Director:      "logs",                       // 保存目录
    LinkName:      "latest_log",                 // 链接文件
    ShowLine:      true,                         // 是否显示行号
    EncodeLevel:   "LowercaseColorLevelEncoder", // 编码级
    StacktraceKey: "stacktrace",                 // 栈名
    LogInConsole:  true,                         // 输出控制台
}

zapApp := core.InitZap(core.Config)
for i := 0; i < 10000000; i++ {
    zapApp.Info("日志内容:", zap.Any("user", "001")) // 写入文件
}
defer zapApp.Sync()

核心配置如下

package core

import (
    "fmt"
    rotatelogs "github.com/lestrrat-go/file-rotatelogs"
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "gopkg.in/natefinch/lumberjack.v2"
    "os"
    "path"
    "time"
    "zaptest/utils"
)

// ZapConf Zap配置结构
type ZapConf struct {
    Level         string `mapstructure:"level" json:"level" yaml:"level"`                           // 级别
    Format        string `mapstructure:"format" json:"format" yaml:"format"`                        // 输出
    Prefix        string `mapstructure:"prefix" json:"prefix" yaml:"prefix"`                        // 日志前缀
    Director      string `mapstructure:"director" json:"director"  yaml:"director"`                 // 日志文件夹
    LinkName      string `mapstructure:"link-name" json:"linkName" yaml:"link-name"`                // 软链接名称
    ShowLine      bool   `mapstructure:"show-line" json:"showLine" yaml:"showLine"`                 // 显示行
    EncodeLevel   string `mapstructure:"encode-level" json:"encodeLevel" yaml:"encode-level"`       // 编码级
    StacktraceKey string `mapstructure:"stacktrace-key" json:"stacktraceKey" yaml:"stacktrace-key"` // 栈名
    LogInConsole  bool   `mapstructure:"log-in-console" json:"logInConsole" yaml:"log-in-console"`  // 输出控制台
}

// Config 全局Zap配置
var Config *ZapConf
var Level zapcore.Level

func InitZap(config *ZapConf) (logger *zap.Logger) {

    // 判断是否有存储日志文件夹
    if ok, _ := utils.PathExists(config.Director); !ok {
        // 不存在创建日志存储目录
        fmt.Printf("create %v directory\n", config.Director)
        _ = os.Mkdir(config.Director, os.ModePerm)
    }

    // 初始化配置文件的Level
    switch config.Level {
    case "debug":
        Level = zap.DebugLevel
    case "info":
        Level = zap.InfoLevel
    case "warn":
        Level = zap.WarnLevel
    case "error":
        Level = zap.ErrorLevel
    case "dpanic":
        Level = zap.DPanicLevel
    case "panic":
        Level = zap.PanicLevel
    case "fatal":
        Level = zap.FatalLevel
    default:
        Level = zap.InfoLevel
    }

    if Level == zap.DebugLevel || Level == zap.ErrorLevel {
        logger = zap.New(getEncoderCore(), zap.AddStacktrace(Level))
    } else {
        logger = zap.New(getEncoderCore())
    }
    // 如果开启行号
    if config.ShowLine {
        logger = logger.WithOptions(zap.AddCaller())
    }
    return logger
}

// CustomTimeEncoder 自定义日志输出时间格式
func CustomTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
    enc.AppendString(t.Format(Config.Prefix + "2006/01/02 - 15:04:05.000"))
}

// getEncoderConfig 获取zapcore.EncoderConfig
func getEncoderConfig() (conf zapcore.EncoderConfig) {
    conf = zapcore.EncoderConfig{
        MessageKey:     "message",
        LevelKey:       "level",
        TimeKey:        "time",
        NameKey:        "logger",
        CallerKey:      "caller",
        StacktraceKey:  Config.StacktraceKey,
        LineEnding:     zapcore.DefaultLineEnding,
        EncodeLevel:    zapcore.LowercaseLevelEncoder,
        EncodeTime:     CustomTimeEncoder,
        EncodeDuration: zapcore.SecondsDurationEncoder,
        EncodeCaller:   zapcore.FullCallerEncoder,
    }
    switch {
    case Config.EncodeLevel == "LowercaseLevelEncoder": // 小写编码器(默认)
        conf.EncodeLevel = zapcore.LowercaseLevelEncoder
    case Config.EncodeLevel == "LowercaseColorLevelEncoder": // 小写编码器带颜色
        conf.EncodeLevel = zapcore.LowercaseColorLevelEncoder
    case Config.EncodeLevel == "CapitalLevelEncoder": // 大写编码器
        conf.EncodeLevel = zapcore.CapitalLevelEncoder
    case Config.EncodeLevel == "CapitalColorLevelEncoder": // 大写编码器带颜色
        conf.EncodeLevel = zapcore.CapitalColorLevelEncoder
    default:
        conf.EncodeLevel = zapcore.LowercaseLevelEncoder
    }
    return conf
}

// 获取核心
func getEncoderCore() (core zapcore.Core) {
    // 按照日期写入文件
    //writer, err := GetWriteSyncerByDate()
    //if err != nil {
    //  fmt.Printf("Get Write Syncer Failed err:%v", err.Error())
    //  return
    //}

    // 按照大小写入文件
    writer := GetWriteSyncerBySize()

    return zapcore.NewCore(getEncoder(), writer, Level)
}

// 判断是json还是console格式化
func getEncoder() zapcore.Encoder {
    if Config.Format == "json" {
        return zapcore.NewJSONEncoder(getEncoderConfig())
    }
    return zapcore.NewConsoleEncoder(getEncoderConfig())
}

// GetWriteSyncerByDate 按照日期分割日志
func GetWriteSyncerByDate() (zapcore.WriteSyncer, error) {
    fileWriter, err := rotatelogs.New(
        //path.Join(Config.Director, "%Y-%m-%d.log"),  // 日志文件名
        //rotatelogs.WithMaxAge(7*24*time.Hour),       // 日志最大保存时间
        //rotatelogs.WithRotationTime(24*time.Minute), // 日志轮转时间
        //rotatelogs.WithLinkName(Config.LinkName),

        //每分钟
        path.Join(Config.Director, "%Y-%m-%d-%H-%M.log"),
        rotatelogs.WithLinkName(Config.LinkName), //生成软链,指向最新日志文件
        rotatelogs.WithRotationTime(time.Minute), //最小为1分钟轮询。默认60s  低于1分钟就按1分钟来
        rotatelogs.WithRotationCount(3),          //设置3份 大于3份 或到了清理时间 开始清理
        rotatelogs.WithRotationSize(1*1024*1024), //设置100MB大小,当大于这个容量时,创建新的日志文件

        //rotatelogs.WithRotationSize(100),
        //rotatelogs.WithRotationTime(24*time.Hour),        // 日志轮转时间
    )
    if err != nil {
        return nil, err
    }

    if Config.LogInConsole {
        return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileWriter)), nil
    }
    return zapcore.AddSync(fileWriter), nil
}

// GetWriteSyncerBySize 按照大小分割日志
func GetWriteSyncerBySize() zapcore.WriteSyncer {
    fileWriter := &lumberjack.Logger{
        Filename:   Config.Director + "/app.log", // 日志文件名
        MaxSize:    1,                            // 每个日志文件的最大尺寸(单位:MB)
        MaxBackups: 5,                            // 保留的旧日志文件的最大数量
        MaxAge:     7,                            // 保留的日志文件最大天数
        Compress:   true,                         // 是否压缩日志文件
    }

    if Config.LogInConsole {
        return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileWriter))
    }
    return zapcore.AddSync(fileWriter)
}

// GetWriteSyncerByDateAndSize 日期日志 + 大小日志
func GetWriteSyncerByDateAndSize() (zapcore.WriteSyncer, error) {
    // 按日期分割
    dateWriter, err := rotatelogs.New(
        path.Join(Config.Director, "%Y-%m-%d.log"),
        rotatelogs.WithMaxAge(7*24*time.Hour),
        rotatelogs.WithRotationTime(24*time.Hour),
    )
    if err != nil {
        return nil, err
    }

    // 按大小分割
    sizeWriter := &lumberjack.Logger{
        Filename:   Config.Director + "/app.log",
        MaxSize:    1, // 1MB
        MaxBackups: 5,
        MaxAge:     7,
        Compress:   true,
    }

    // 同时输出到日期和大小分割的日志
    if Config.LogInConsole {
        return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(dateWriter), zapcore.AddSync(sizeWriter)), nil
    }
    return zapcore.NewMultiWriteSyncer(zapcore.AddSync(dateWriter), zapcore.AddSync(sizeWriter)), nil
}

// PathExists 检查路径是否存在
func PathExists(path string) (bool, error) {
    _, err := os.Stat(path)
    if err == nil {
        return true, nil
    }
    if os.IsNotExist(err) {
        return false, nil
    }
    return false, err
}
1 2 3 4 5 6 11