教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 GO语言-文件版日志系统

GO语言-文件版日志系统

发布时间:2021-12-26   编辑:jiaochengji.com
教程集为您提供GO语言-文件版日志系统等资源,欢迎您收藏本站,我们将为您提供最新的GO语言-文件版日志系统资源
<h1>一、背景</h1>

春节期间在B站学习了GO语言的函数,方法,指针,切片,结构体,文件,Map,接口,time等基础知识,需要一个项目进行综合性练手来更好的理解这些基础,所以编写了一个日志系统,供正在学习GO语言的同学参考。

<h1>二、系统原理解析</h1> <h2>2.1 目录结构</h2> <ul><li>main包下的main.go</li></ul>

 

<ul><li>mylogger包下的 console.go,file.go,mylogger.go</li></ul>

<h2>2.2 main包下的go文件</h2> <h3>2.2.1 main.go</h3> <ul><li>通过调用mylogger包下的不同的构造方法【NewConsoleLogger()或NewFileLogger()】,会返回不同的对象</li><li>返回的ConsoleLogger或*FileLogger类型的对象可以赋值给mylogger包下的LoggerInterface接口,因为这两类对象全部实现了接口中定义的方法【Debug,Info,Warn...】</li><li>通过写一个for{}循环模拟日志输出,输出到console还是文件根据调用的构造方法返回不同类型的对象决定</li></ul><pre class="has"><code class="language-Go">package main import ( "time" mylogger "github.com/studygo/day01/07_mylogger" ) var log mylogger.LoggerInterface //在main包里面定义log变量,可以在main包的其他文件中使用 func main() { log := mylogger.NewConsoleLogger("Info") // log = mylogger.NewFileLogger("info", "./", "accessTest.log", 10*1024*1024) for { log.Debug("这是一条Debug日志") log.Info("这是一条Info日志") log.Warn("这是一条Warn日志") id := 10010 name := "kelly" log.Error("这是一条Error日志,id:%d,name:%s", id, name) log.Fatal("这是一条Fetal日志") // fmt.Printf("id:%d,name:%s", id, name) log.Fetal("这是一条Fetal日志") time.Sleep(2 * time.Second) } } </code></pre> <h3>2.3 mylogger包下的go文件</h3> <h3>2.2.1 日志系统的公共变量和方法</h3>

1. mylogger.go

<ul><li>定义了一个自定义类型为LogLevel,不同于类型别名的是自定义类型在编译完成之后这种类型依然存在。详细参考:类型别名和自定义类型区别</li><li>定义DEBUG,INFO,WARN,ERROR,FATAL等LogLevel类型的常量</li><li>定义LoggerInterface接口,这个接口下定义了五大抽象方法,只要实现了这些方法的对象就可以称为LoggerInterface类型。详细参考:GO语言接口</li><li>定义将字符串解析为LogLevel类型的方法和将LogLevel类型方法转换成string类型的方法</li></ul><pre class="has"><code class="language-Go">package mylogger /* 1.支持往不同的地方输出日志 2.日志分级别 1.Debug 2.Trace 3.Info 4.Warning 5.Error 6.Fatal 3.日志需要开关控制,比如说开发的时候什么级别都能输出,但是上线之后只能有INFO级别往下的日志才可以输出 4.日志要有时间,行号(runtime.Caller()),文件名,日志级别,日志信息 5.日志文件要切割 5.1 按文件大小切割 5.2 按文件日期区分 */ //往终端写日志相关的内容 import ( "errors" "strings" ) //LogLevel 等级 type LogLevel uint16 const ( // UNKNOW switch中的默认参数 UNKNOW LogLevel = iota //DEBUG ... DEBUG //TRACE ... TRACE //INFO ... INFO //WARN ... WARN //ERROR ... ERROR //FATAL ... FATAL ) //LoggerInterface 日志接口,可以用来接收console和file的返回值 type LoggerInterface interface { Debug(msg string, arg ...interface{}) Info(msg string, arg ...interface{}) Warn(msg string, arg ...interface{}) Error(msg string, arg ...interface{}) Fatal(msg string, arg ...interface{}) } func parseLogLevel(s string) (LogLevel, error) { s = strings.ToLower(s) switch s { case "debug": return DEBUG, nil case "trace": return TRACE, nil case "info": return INFO, nil case "warn": return WARN, nil case "error": return ERROR, nil case "fatal": return FATAL, nil default: err := errors.New("无效的日志级别") return UNKNOW, err } } func getLogString(lv LogLevel) string { switch lv { case DEBUG: return "DEBUG" case TRACE: return "TRACE" case INFO: return "INFO" case WARN: return "WARING" case ERROR: return "ERROR" case FATAL: return "FATAL" default: return "UNKNOW" } } </code></pre> <h3>2.2.2 控制台输出日志信息</h3>

1. file.go

<ul><li>定义了NewFileLogger构造函数,返回的是一个FileLogger类型的结构体指针,通过parseLogLevel()方法把字符串类型转换成LogLevel类型,构造函数还需要做的是根据传入的文件路径和文件名打开文件,为了方便开发查看ERROR以上级别的日志,还需要把这些日志单独记录到一个文件中,最后把fileObj,errObj赋值给指针变量f的fileObj,errorObj字段。</li><li>使用file.go文件下的Debug,Info,Warn,Error等方法,这些方法会调用enable进行判断是否高于指定日志级别【本文中在构造函数中传入的日志级别为Info,filepath为当前目录 "./",filename为 "accessTest.log",文件切割临界为 "10*1024*1024"(10KB)】,如果返回为true则调用具体的log方法在文件中记录日志信息,反之则不会打印打文件</li><li>log方法具体实现就是先判断日志级别是否大于等于传入的级别,返回true则继续调用checkSize方法判断文件大小是否大于传入的临界值,返回true则继续调用split方法对日志进行切割,详细原理见下方代码</li><li>额外的,如果要记录的日志大于等于ERROR级别,我还要在err日志中再记录一遍</li></ul><pre class="has"><code class="language-Go">package mylogger import ( "fmt" "os" "path" "time" ) //FileLogger 往文件里面写日志相关代码 文件日志结构体 type FileLogger struct { Level LogLevel filePath string fileName string fileObj *os.File errorObj *os.File maxFileSize int64 } //NewFileLogger 构造方法 func NewFileLogger(levelStr, fp, fn string, maxSize int64) *FileLogger { logLevel, err := parseLogLevel(levelStr) if err != nil { panic(err) } fl := &FileLogger{ Level: logLevel, filePath: fp, fileName: fn, maxFileSize: maxSize, } err = fl.initFile() //按照文件路径和文件名将文件打开 if err != nil { panic(err) } return fl } //根据指定的文件路径和文件名打开文件 func (f *FileLogger) initFile() error { fullName := path.Join(f.filePath, f.fileName) fileObj, err := os.OpenFile(fullName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { fmt.Printf("open log file failed,err:%v\n", err) return err } errObj, err := os.OpenFile(fullName ".err", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { fmt.Printf("open err log file failed,err:%v\n", err) return err } //日志文件都已经打开 f.fileObj = fileObj f.errorObj = errObj return nil } //判断是否需要记录该日志 func (f *FileLogger) enable(logLevel LogLevel) bool { return logLevel >= f.Level } //根据大小判断文件是否需要切割 func (f *FileLogger) checkSize(file *os.File) bool { fileInfo, err := file.Stat() if err != nil { panic(err) } return fileInfo.Size() >= f.maxFileSize //如果传入的文件大小大于等于文件日志的最大值,就应该返回true } //切割文件 func (f *FileLogger) splitFile(file *os.File) (*os.File, error) { // 需要切割日志文件 nowStr := time.Now().Format("20160102150405000") fileInfo, err := file.Stat() if err != nil { fmt.Printf("get file info failed,err:%v\n", err) return nil, err } logName := path.Join(f.filePath, fileInfo.Name()) //拿到当前日志文件完整路径 newLogName := fmt.Sprintf("%s.bak%s", logName, nowStr) //拼接一个日志文件备份的名字 //1.关闭当前日志文件 file.Close() //2.备份一下 rename os.Rename(logName, newLogName) //3.打开一个新的日志文件 fileObj, err := os.OpenFile(logName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { fmt.Printf("file open failed:err:%v", err) return nil, err } //4.将打开的日志文件对象赋值给 f.fileObj return fileObj, nil } //记录日志的方法 func (f *FileLogger) log(lv LogLevel, msg string, arg ...interface{}) { if f.enable(lv) { // fmt.Println(msg) fullMsg := fmt.Sprintf(msg, arg...) // fmt.Println(fullMsg) now := time.Now() funcName, fileName, lineNo := getInfo(3) if f.checkSize(f.fileObj) { newFile, err := f.splitFile(f.fileObj) if err != nil { return } f.fileObj = newFile } fmt.Fprintf(f.fileObj, "[%s] [%s] [%s:%s:%d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), funcName, fileName, lineNo, fullMsg) if lv >= ERROR { //如果要记录的日志大于等于ERROR级别,我还要在err日志中再记录一遍 if f.checkSize(f.fileObj) { newFile, err := f.splitFile(f.fileObj) if err != nil { return } f.fileObj = newFile } fmt.Fprintf(f.errorObj, "[%s] [%s] [%s:%s:%d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), funcName, fileName, lineNo, fullMsg) } } } //Debug 方法 func (f *FileLogger) Debug(msg string, arg ...interface{}) { f.log(DEBUG, msg, arg...) } //Info 方法 func (f *FileLogger) Info(msg string, arg ...interface{}) { f.log(INFO, msg, arg...) } //Warn 方法 func (f *FileLogger) Warn(msg string, arg ...interface{}) { f.log(INFO, msg, arg...) } //Error 方法 func (f *FileLogger) Error(msg string, arg ...interface{}) { f.log(ERROR, msg, arg...) } //Fatal 方法 func (f *FileLogger) Fatal(msg string, arg ...interface{}) { f.log(FATAL, msg, arg...) } </code></pre>

2.日志输出到文件结果验证

<ul><li>在main.go的同级目录下新增了accessTest.log,accessTest.log.bak202002011437...</li><li>通过查看文件大小发现文件大小为10KB,说明已经按照我们预期的文件大小阈值进行切割了</li><li>accessTest.log中包含大于等于指定类型的日志信息,accessTest.log.err中包含大于等于ERROR类型的日志信息</li></ul>

 

到此这篇关于“GO语言-文件版日志系统”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
GO语言-文件版日志系统
2018年最全Go语言教程零基础入门到进阶实战视频
Go语言发展历史、核心、特性及学习路线
golang日志服务器_深扒GO日志 | (一)从Go语言的日志包说起
查看go 安装了哪些包_go语言基础入门(一)
想系统学习GO语言(Golang
[GO语言基础] 一.为什么我要学习Golang以及GO语言入门普及
Ubuntu18.04安装Go语言开发环境
Go 开发关键技术指南 | 为什么你要选择 Go?(内含超全知识大图)
Go 语言十年而立,Go2 蓄势待发

[关闭]
~ ~