其他分享
首页 > 其他分享> > Gin实践 连载五 编写一个简单的文件日志

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

作者:互联网

编写一个简单的文件日志

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

新建logging包

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

编写file文件
  1. file.go
package logging

import (
	"errors"
	"fmt"
	"io/fs"
	"log"
	"os"
	"time"
)

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 errors.Is(err, fs.ErrNotExist):
		mkDir()
	case errors.Is(err, fs.ErrPermission):
		log.Fatalf("Permission:%v", err)
	}

	handler, 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 handler
}
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
  }
  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  // 如果可以,打开时
  )

编写log文件

  1. log.go
package logging

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"runtime"
)

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
	WARN
	ERROR
	FATAL
)

func init() {
	filePath := getLogFileFullPath()
	F = openLogFile(filePath)
	logger = log.New(F, DefaultPrefix, log.LstdFlags)
}
func Debug(v ...any) {
	setPrefix(DEBUG)
	logger.Println(v)
}
func Info(v ...any) {
	setPrefix(INFO)
	logger.Println(v)
}
func Warn(v ...any) {
	setPrefix(WARN)
	logger.Println(v)
}
func Error(v ...any) {
	setPrefix(ERROR)
	logger.Println(v)
}
func Fatal(v ...any) {
	setPrefix(FATAL)
	logger.Fatal(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
	Lmsgprefix                    // move the "prefix" from the beginning of the line to before the message
	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.go、tag.go、auth.go
  2. 将log包的引用删除,修改引用我们自己的日志包为gin-blog/pkg/logging
  3. 将原本的log.Println(...)改为logging.Info(...)

验证功能

修改文件后,重启服务,我们来试试吧!
获取到API的Token后,我们故意传错误URL参数给接口,如:http://127.0.0.1:8000/api/v1/articles?tag_id=0&state=9999999&token=eyJhbG..
然后我们到$GOPATH/gin-blog/runtime/logs查看日志:
log20220910.log

[INFO][article.go:170]2022/09/10 00:00:40 [modified_by 修改人不能为空]
[INFO][article.go:170]2022/09/10 00:00:47 [state 状态只允许0或1]

日志结构一切正常,我们的记录模式都为Info,因此前缀是对的,并且我们是入参有问题,也把错误记录下来了,这样排错就很方便了!
至此,本节就完成了,这只是一个简单的扩展,实际上我们线上项目要使用的文件日志,是更复杂一些,开动你的大脑 举一反三吧!

定时任务实现按天切割日志

虽然我们的日志名称中是按天的格式的,但是上面我们并没有实现按天进行日志的切割,为了实现日志按天切割,我们需要引入一个定时任务:
在pkg目录下创建cron目录,新建cron.go文件:

package cron

import (
	"time"
)

// SetTime 获取到自定时间的Duration 误差在1s内
// 计算设置时间和当前时间的差值,大于当前时间则返回,否则为第二天的时间
func SetTime(hour, minute, second int) (d time.Duration) {
	now := time.Now()
	setTime := time.Date(now.Year(), now.Month(), now.Day(), hour, minute, second, 0, now.Location())
	d = setTime.Sub(now)
	if d > 0 {
		return
	}
	return d + time.Hour*24
}
func ScheduleTask(f func()) {
	timer := time.NewTimer(SetTime(0, 0, 0))
	defer timer.Stop()
	for {
		select {
		case <-timer.C:
			timer.Reset(time.Hour * 24)
			f()
		}
	}
}

pkg/logging/log.go文件修改:

func init() {
	filePath := getLogFileFullPath()
	F = openLogFile(filePath)
	logger = log.New(F, DefaultPrefix, log.LstdFlags)
	go cron.ScheduleTask(updateF)
}
func updateF() {
	filePath := getLogFileFullPath()
	F = openLogFile(filePath)
	logger = log.New(F, DefaultPrefix, log.LstdFlags)
}

这样的话每天00:00:00秒就会创建新的日志文件:

标签:文件,连载,func,go,Gin,日志,os,log
来源: https://www.cnblogs.com/mayanan/p/16672187.html