涉及知识点
-
自定义 log。
本文目标
在上一节中,我们解决了 API's 可以任意访问的问题,那么我们现在还有一个问题,就是我们的日志,都是输出到控制台上的,这显然对于一个项目来说是不合理的,因此我们这一节简单封装log库,使其支持简单的文件日志!
新建logging包
我们在pkg下新建logging目录,新建file.go和log.go文件,写入内容:
编写file文件
1、 file.go:
package loggingimport ("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 stringPath stringErr error}
-
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 // 同步IOO_TRUNC int = syscall.O_TRUNC // 如果可以,打开时)
-
os.Getwd:返回与当前目录对应的根路径名 -
os.MkdirAll:创建对应的目录以及所需的子目录,若成功则返回nil,否则返回error -
os.ModePerm:const定义ModePerm FileMode = 0777
编写log文件
2、log.go
package loggingimport ("log""os""runtime""path/filepath""fmt")type Level intvar (F *os.FileDefaultPrefix = ""DefaultCallerDepth = 2logger *log.LoggerlogPrefix = ""levelFlags = []string{"DEBUG", "INFO", "WARN", "ERROR", "FATAL"})const (DEBUG Level = iotaINFOWARNINGERRORFATAL)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/23Ltime // the time in the local time zone: 01:23:23Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime.Llongfile // full file name and line number: /a/b/c/d.go:23Lshortfile // final file name element and line number: d.go:23. overrides LlongfileLUTC // if Ldate or Ltime is set, use UTC rather than the local time zoneLstdFlags = 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包的代码,如下:
-
打开 routers目录下的article.go、tag.go、auth.go。 -
将 log包的引用删除,修改引用我们自己的日志包为github.com/EDDYCJY/go-gin-example/pkg/logging。 -
将原本的 log.Println(...)改为logging.Info(...)。
例如auth.go文件的修改内容:
package apiimport ("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_PARAMSif 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
我的公众号

文章评论