Go Gin 系列六:编写一个简单的文件日志

涉及知识点

  • 自定义 log。

本文目标

在上一节中,我们解决了 API's 可以任意访问的问题,那么我们现在还有一个问题,就是我们的日志,都是输出到控制台上的,这显然对于一个项目来说是不合理的,因此我们这一节简单封装log库,使其支持简单的文件日志!

新建logging

我们在pkg下新建logging目录,新建file.golog.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()
		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)
	}
}
  • os.Stat:返回文件信息结构描述文件。如果出现错误,会返回*PathError

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

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

  • 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  // 如果可以,打开时
)
  • os.Getwd:返回与当前目录对应的根路径名

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

  • os.ModePermconst定义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)
}


  • log.New:创建一个新的日志记录器。out定义要写入日志数据的IO句柄。prefix定义每个生成的日志行的开头。flag定义了日志记录属性

func New(out io.Writer, prefix string, flag int) *Logger {
    return &Logger{out: out, prefix: prefix, flag: flag}
}
  • 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
)

当前目录结构:

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


我们自定义的logging包,已经基本完成了,接下来让它接入到我们的项目之中吧。我们打开先前包含log包的代码,如下:

  1. 打开routers目录下的article.gotag.goauth.go

  2. log包的引用删除,修改引用我们自己的日志包为github.com/EDDYCJY/go-gin-example/pkg/logging

  3. 将原本的log.Println(...)改为logging.Info(...)

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

package api


import (
	"net/http"


	"github.com/gin-gonic/gin"
	"github.com/astaxie/beego/validation"


	"github.com/EDDYCJY/go-gin-example/pkg/e"
	"github.com/EDDYCJY/go-gin-example/pkg/util"
	"github.com/EDDYCJY/go-gin-example/models"
	"github.com/EDDYCJY/go-gin-example/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,
    })
}

验证功能

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

获取到 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]

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

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

参考

本系列示例代码

  • go-gin-example

我的公众号

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值