Gin实践 连载五 编写一个简单的文件日志
作者:互联网
编写一个简单的文件日志
在上一节中,我们解决了API’s可以任意访问的问题,那么我们现在还有一个问题。
就是我们的日志,都是输出到控制台上的,这显然对于一个项目来说是不合理的,因此我们这一节简单封装log库,使其支持简单的文件日志!
新建logging包
我们在pkg下新建logging目录,新建file.go和log.go文件,写入内容:
编写file文件
- 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
}
- 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 // 如果可以,打开时
)
- os.Getwd:返回与当前目录对应的根路径名
- os.MkdirAll:创建对应的目录以及所需的子目录,若成功则返回nil,否则返回error
- os.ModePerm:const定义ModePerm FileMode = 0777
编写log文件
- 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包的代码,
- 打开routers目录下的article.go、tag.go、auth.go
- 将log包的引用删除,修改引用我们自己的日志包为gin-blog/pkg/logging
- 将原本的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