扫码订阅《 》或入驻星球,即可阅读文章!

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go友会
  • 城市
  • 校园
Go学院
  • Go小课
  • Go小考
  • Go实战
  • 精品课
Go求职
  • 求职辅导🔥
  • Offer收割社群
  • 企业题库
  • 面试宝典
Go宝典
  • 在线宝典
  • B站精选
  • 推荐图书
  • 每日博文
Go仓库
实验区
  • Go周边
  • Go下载
  • Go月刊
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
author-avatar

GOLANG ROADMAP


首页
Go友会
  • 城市
  • 校园
Go学院
  • Go小课
  • Go小考
  • Go实战
  • 精品课
Go求职
  • 求职辅导🔥
  • Offer收割社群
  • 企业题库
  • 面试宝典
Go宝典
  • 在线宝典
  • B站精选
  • 推荐图书
  • 每日博文
Go仓库
实验区
  • Go周边
  • Go下载
  • Go月刊
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
  • 宝典简介

  • gin系列目录

    • Golang介绍与环境安装
    • Gin搭建Blog API’s (一)
    • Gin实践 连载三 搭建Blog API’s(二)
    • Gin实践 连载四 搭建Blog API’s(三)
    • Gin实践 连载五 使用JWT进行身份校验
    • Gin实践 连载六 编写一个简单的文件日志
    • Gin实践 连载七 Golang优雅重启HTTP服务
    • Gin实践 连载八 为它加上Swagger
    • Gin实践 连载九 将Golang应用部署到Docker
    • Gin实践 连载十 定制 GORM Callbacks
    • Gin实践 连载十一 Cron定时任务
    • Gin实践 连载十二 优化配置结构及实现图片上传
    • Gin实践 连载十三 优化你的应用结构和实现Redis缓存
    • Gin实践 连载十四 实现导出、导入 Excel
    • Gin实践 连载十五 生成二维码、合并海报
    • Gin实践 连载十六 在图片上绘制文字
    • Gin实践 连载十七 用 Nginx 部署 Go 应用
    • Gin实践 番外 Golang交叉编译
    • Gin实践 番外 请入门 Makefile
  • 杂谈

  • 爬虫系列目录

  • gRPC系列目录

扫码订阅《 》或入驻星球,即可阅读文章!

Gin实践 连载六 编写一个简单的文件日志


煎鱼

在上一节中,我们解决了API’s可以任意访问的问题,那么我们现在还有一个问题。

就是我们的日志,都是输出到控制台上的,这显然对于一个项目来说是不合理的,因此我们这一节简单封装log库,使其支持简单的文件日志!

项目地址:https://github.com/EDDYCJY/go-gin-example


# 新建logging包

我们在pkg下新建logging目录,新建file.go和log.go文件,写入内容:

# 编写file文件

1、 file.go:

package logging
import (
    "os"
    "time"
    "fmt"
    "log"
)
var (
    LogSavePath = "runtime/logs/"
    LogSaveName = "log"
    LogFileExt = "log"
    TimeFormat = "20060102"
)
func getLogFilePath() string {
    return fmt.Sprintf("%s", LogSavePath)
}
func getLogFileFullPath() string {
    prefixPath := getLogFilePath()
    suffixPath := fmt.Sprintf("%s%s.%s", LogSaveName, time.Now().Format(TimeFormat), LogFileExt)
    return fmt.Sprintf("%s%s", prefixPath, suffixPath)
}
func openLogFile(filePath string) *os.File {
    _, err := os.Stat(filePath)
    switch {
        case os.IsNotExist(err):
            mkDir(getLogFilePath())
        case os.IsPermission(err):
            log.Fatalf("Permission :%v", err)
    }
    handle, err := os.OpenFile(filePath, os.O_APPEND | os.O_CREATE | os.O_WRONLY, 0644)
    if err != nil {
        log.Fatalf("Fail to OpenFile :%v", err)
    }
    return handle
}
func mkDir() {
    dir, _ := os.Getwd()
    err := os.MkdirAll(dir + "/" + getLogFilePath(), os.ModePerm)
    if err != nil {
        panic(err)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
  • os.Stat:返回文件信息结构描述文件。如果出现错误,会返回*PathError

      type PathError struct {
          Op   string
          Path string
          Err  error
      }
    
    1
    2
    3
    4
    5
  • os.IsNotExist:能够接受ErrNotExist、syscall的一些错误,它会返回一个布尔值,能够得知文件不存在或目录不存在

  • os.IsPermission:能够接受ErrPermission、syscall的一些错误,它会返回一个布尔值,能够得知权限是否满足

  • os.OpenFile:调用文件,支持传入文件名称、指定的模式调用文件、文件权限,返回的文件的方法可以用于I/O。如果出现错误,则为*PathError。

      const (
          // Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
          O_RDONLY int = syscall.O_RDONLY // 以只读模式打开文件
          O_WRONLY int = syscall.O_WRONLY // 以只写模式打开文件
          O_RDWR   int = syscall.O_RDWR   // 以读写模式打开文件
          // The remaining values may be or'ed in to control behavior.
          O_APPEND int = syscall.O_APPEND // 在写入时将数据追加到文件中
          O_CREATE int = syscall.O_CREAT  // 如果不存在,则创建一个新文件
          O_EXCL   int = syscall.O_EXCL   // 使用O_CREATE时,文件必须不存在
          O_SYNC   int = syscall.O_SYNC   // 同步IO
          O_TRUNC  int = syscall.O_TRUNC  // 如果可以,打开时
      )
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  • os.Getwd:返回与当前目录对应的根路径名

  • os.MkdirAll:创建对应的目录以及所需的子目录,若成功则返回nil,否则返回error

  • os.ModePerm:const定义ModePerm FileMode = 0777

# 编写log文件

2、 log.go

package logging
import (
    "log"
    "os"
    "runtime"
    "path/filepath"
    "fmt"
)
type Level int
var (
    F *os.File
    DefaultPrefix = ""
    DefaultCallerDepth = 2
    logger *log.Logger
    logPrefix = ""
    levelFlags = []string{"DEBUG", "INFO", "WARN", "ERROR", "FATAL"}
)
const (
    DEBUG Level = iota
    INFO
    WARNING
    ERROR
    FATAL
)
func init() {
    filePath := getLogFileFullPath()
    F = openLogFile(filePath)
    logger = log.New(F, DefaultPrefix, log.LstdFlags)
}
func Debug(v ...interface{}) {
    setPrefix(DEBUG)
    logger.Println(v)
}
func Info(v ...interface{}) {
    setPrefix(INFO)
    logger.Println(v)
}
func Warn(v ...interface{}) {
    setPrefix(WARNING)
    logger.Println(v)
}
func Error(v ...interface{}) {
    setPrefix(ERROR)
    logger.Println(v)
}
func Fatal(v ...interface{}) {
    setPrefix(FATAL)
    logger.Fatalln(v)
}
func setPrefix(level Level) {
    _, file, line, ok := runtime.Caller(DefaultCallerDepth)
    if ok {
        logPrefix = fmt.Sprintf("[%s][%s:%d]", levelFlags[level], filepath.Base(file), line)
    } else {
        logPrefix = fmt.Sprintf("[%s]", levelFlags[level])
    }
    logger.SetPrefix(logPrefix)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
  • log.New:创建一个新的日志记录器。out定义要写入日志数据的IO句柄。prefix定义每个生成的日志行的开头。flag定义了日志记录属性

      func New(out io.Writer, prefix string, flag int) *Logger {
          return &Logger{out: out, prefix: prefix, flag: flag}
      }
    
    1
    2
    3
  • log.LstdFlags:日志记录的格式属性之一,其余的选项如下

      const (
          Ldate         = 1 << iota     // the date in the local time zone: 2009/01/23
          Ltime                         // the time in the local time zone: 01:23:23
          Lmicroseconds                 // microsecond resolution: 01:23:23.123123.  assumes Ltime.
          Llongfile                     // full file name and line number: /a/b/c/d.go:23
          Lshortfile                    // final file name element and line number: d.go:23. overrides Llongfile
          LUTC                          // if Ldate or Ltime is set, use UTC rather than the local time zone
          LstdFlags     = Ldate | Ltime // initial values for the standard logger
      )
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

当前目录结构:

gin-blog/
├── conf
│   └── app.ini
├── main.go
├── middleware
│   └── jwt
│       └── jwt.go
├── models
│   ├── article.go
│   ├── auth.go
│   ├── models.go
│   └── tag.go
├── pkg
│   ├── e
│   │   ├── code.go
│   │   └── msg.go
│   ├── logging
│   │   ├── file.go
│   │   └── log.go
│   ├── setting
│   │   └── setting.go
│   └── util
│       ├── jwt.go
│       └── pagination.go
├── routers
│   ├── api
│   │   ├── auth.go
│   │   └── v1
│   │       ├── article.go
│   │       └── tag.go
│   └── router.go
├── runtime
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

我们自定义的logging包,已经基本完成了,接下来让它接入到我们的项目之中吧!

我们打开先前包含log包的代码,

  1. 打开routers目录下的article.go、tag.go、auth.go
  2. 将log包的引用删除,修改引用我们自己的日志包为gin-blog/pkg/logging
  3. 将原本的log.Println(...)改为log.Info(...)

例如auth.go文件的修改内容:

package api
import (
    "net/http"
    "github.com/gin-gonic/gin"
    "github.com/astaxie/beego/validation"
    "gin-blog/pkg/e"
    "gin-blog/pkg/util"
    "gin-blog/models"
    "gin-blog/pkg/logging"
)
...
func GetAuth(c *gin.Context) {
    ...
    code := e.INVALID_PARAMS
    if ok {
        ...
    } else {
        for _, err := range valid.Errors {
            logging.Info(err.Key, err.Message)
        }
    }
    c.JSON(http.StatusOK, gin.H{
        "code" : code,
        "msg" : e.GetMsg(code),
        "data" : data,
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 验证功能

修改文件后,重启服务,我们来试试吧!

获取到API的Token后,我们故意传错误URL参数给接口,如:http://127.0.0.1:8000/api/v1/articles?tag_id=0&state=9999999&token=eyJhbG..

然后我们到$GOPATH/gin-blog/runtime/logs查看日志:

$ tail -f log20180216.log 
[INFO][article.go:79]2018/02/16 18:33:12 [state 状态只允许0或1]
[INFO][article.go:79]2018/02/16 18:33:42 [state 状态只允许0或1]
[INFO][article.go:79]2018/02/16 18:33:42 [tag_id 标签ID必须大于0]
[INFO][article.go:79]2018/02/16 18:38:39 [state 状态只允许0或1]
[INFO][article.go:79]2018/02/16 18:38:39 [tag_id 标签ID必须大于0]
1
2
3
4
5
6

日志结构一切正常,我们的记录模式都为Info,因此前缀是对的,并且我们是入参有问题,也把错误记录下来了,这样排错就很方便了!

至此,本节就完成了,这只是一个简单的扩展,实际上我们线上项目要使用的文件日志,是更复杂一些,开动你的大脑 举一反三吧!

# 参考

# 本系列示例代码

  • go-gin-example (opens new window)
  • 新建logging包
  • 编写file文件
  • 编写log文件
  • 验证功能
  • 参考
  • 本系列示例代码