日志库
-
需求分析
- 支持往不同的地方输出日志
- 日志分多种级别:Debug、Trace、Info、Warning、Error、Fatal
- 日志支持控制不同级别的日志开关,支撑开发和上线运行的不同期间支持不同类型的日志(如开发时支持所有日志,上线后只支持INFO级别及以下)
- 日志展示时间、行号、文件名、日志级别、 日志信息
- 日志文件要切割(按照文件大小/日期切割),按照文件大小只需要每次切割前查看文件大小是否达到最大文件,按照日期则可以再结构体设置标志位记录上一次切割的小时数,每次判断当前的时间的小时数和之前的是否一致。
-
runtime.Caller
调用
runtime.Caller
可以查看调用函数的文件地址,函数名,以及行号,注意参数中的数字表示向上返回多少级的意思package main import ( "fmt" "path" "runtime" ) func f2() { //2表示向上返回两级,表示调用这个函数的函数,然后再向上找这个函数的调用函数 pc, file, line, ok := runtime.Caller(2) if !ok { fmt.Println("runtime.Caller() failed") return } funcName := runtime.FuncForPC(pc).Name() fmt.Println(funcName) fmt.Println(file) fmt.Println(path.Base(file)) //只要当前文件的名字 fmt.Println(line) } func f1() { //1表示向上返回一级,返回的是调用这个函数的函数的信息 pc, file, line, ok := runtime.Caller(1) if !ok { fmt.Println("runtime.Caller() failed") return } funcName := runtime.FuncForPC(pc).Name() fmt.Println(funcName) fmt.Println(file) fmt.Println(path.Base(file)) //只要当前文件的名字 fmt.Println(line) f2() } //runtime.Caller返回调用函数名,所在文件名以及行号,参数则是确认返回的是当前函数还是调用这个函数的函数 func main() { //0表示就是当前函数 pc, file, line, ok := runtime.Caller(0) if !ok { fmt.Println("runtime.Caller() failed") return } funcName := runtime.FuncForPC(pc).Name() fmt.Println(funcName) //返回文件名.函数名 fmt.Println(file) fmt.Println(path.Base(file)) //只要当前文件的名字 fmt.Println(line) f1() }
-
fileStat
调用文件类型的
Stat()
可以查看当前的文件信息,如大小,名称等package main import ( "fmt" "os" ) func main() { fileObj, err := os.Open("./main.go") if err != nil { fmt.Printf("open file failed err:%v", err) return } fileInfo, err := fileObj.Stat() if err != nil { fmt.Printf("get fileInfo failed err:%v", err) return } fmt.Printf("文件大小是:%dB\n", fileInfo.Size()) fmt.Printf("文件名是:%s\n", fileInfo.Name()) }
-
日志库公共插件
建立接口,常量,级别设置,设置格式化输出内容
package mylogger import ( "errors" "fmt" "path" "runtime" "strings" ) type LogLevel uint16 type Logger interface { Debug(format string, a ...interface{}) Trace(format string, a ...interface{}) Info(format string, a ...interface{}) Warning(format string, a ...interface{}) Error(format string, a ...interface{}) Fatal(format string, a ...interface{}) } //设置日志级别 const ( UNKNOWN LogLevel = iota DEBUG TRACE INFO WARNING ERROR FATAL ) //将日志级别转换为内置的级别 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 "warning": return WARNING, nil case "error": return ERROR, nil case "fatal": return FATAL, nil default: err := errors.New("无效的日志级别") return UNKNOWN, err } } func getLogString(lv LogLevel) string { switch lv { case DEBUG: return "DEBUG" case TRACE: return "TRACE" case INFO: return "INFO" case WARNING: return "WARNING" case ERROR: return "ERROR" case FATAL: return "FATAL" default: return "UNKNOWN" } } func getInfo(skip int) (funcName, fileName string, lineNo int) { pc, file, lineNo, ok := runtime.Caller(skip) if !ok { fmt.Println("runtime.Caller() failed") return } funcName = runtime.FuncForPC(pc).Name() //只要函数名,不要上面的文件名 funcName = strings.Split(funcName, ".")[1] fileName = path.Base(file) return }
-
日志库终端版
package mylogger import ( "fmt" "time" ) // 往终端写日志内容 //ConsoleLogger日志结构体 type ConsoleLogger struct { Level LogLevel } //Logger构造函数 func NewConsoleLog(levelStr string) ConsoleLogger { level, err := parseLogLevel(levelStr) if err != nil { panic(err) } return ConsoleLogger{ Level: level, } } func (c ConsoleLogger) log(lv LogLevel, format string, a ...interface{}) { if c.enable(lv) { msg := fmt.Sprintf(format, a...) now := time.Now() funcName, fileName, lineNo := getInfo(3) fmt.Printf("[%s] [%s] [%s:%s:%d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), fileName, funcName, lineNo, msg) } } func (c ConsoleLogger) enable(loglevel LogLevel) bool { return c.Level <= loglevel } func (c ConsoleLogger) Debug(format string, a ...interface{}) { c.log(DEBUG, format, a...) } func (c ConsoleLogger) Trace(format string, a ...interface{}) { c.log(TRACE, format, a...) } func (c ConsoleLogger) Info(format string, a ...interface{}) { c.log(INFO, format, a...) } func (c ConsoleLogger) Warning(format string, a ...interface{}) { c.log(WARNING, format, a...) } func (c ConsoleLogger) Error(format string, a ...interface{}) { c.log(ERROR, format, a...) } func (c ConsoleLogger) Fatal(format string, a ...interface{}) { c.log(FATAL, format, a...) }
-
日志库文件版
支持按照文件大小/时间进行切割
package mylogger import ( "fmt" "os" "path" "time" ) // 往文件里面写日志 //日志文件结构体 type FileLogger struct { Level LogLevel filePath string //日志文件保存的路径 fileName string //日志文件保存的文件名 fileObj *os.File errFileObj *os.File maxFileSize int64 //最大文件大小 timeFlag int //日志时间标志 timeErrFlag int //错误日志时间标志 } //FileLogger构造函数 func NewFileLogger(levelStr, fp, fn string, maxSize int64) *FileLogger { LogLevel, err := parseLogLevel(levelStr) if err != nil { panic(err) } tf := time.Now().Minute() tef := time.Now().Minute() fl := &FileLogger{ Level: LogLevel, filePath: fp, fileName: fn, maxFileSize: maxSize, timeFlag: tf, timeErrFlag: tef, } //按照文件路径和文件名将文件打开 err = fl.initFile() if err != nil { panic(err) } return fl } //根据指定的日志文件路径和文件名打开对应日志和错误日志 func (f *FileLogger) initFile() error { //将文件路径和文件名按照操作系统的格式进行拼接 fullFileName := path.Join(f.filePath, f.fileName) //打开日志和日志错误文件 fileObj, err := os.OpenFile(fullFileName, 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 } errfileObj, err := os.OpenFile(fullFileName+".err", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { fmt.Printf("open errlog file failed,err:%v\n", err) return err } f.fileObj = fileObj f.errFileObj = errfileObj return nil } func (f *FileLogger) Close() { f.fileObj.Close() f.errFileObj.Close() } //根据级别确定是否需要记录该日志 func (f *FileLogger) enable(loglevel LogLevel) bool { return f.Level <= loglevel } // 判断文件大小 // func (f *FileLogger) checkSize(file *os.File) bool { // fileInfo, err := file.Stat() // if err != nil { // fmt.Printf("get file info failed,err:%v", err) // return false // } // //返回当前文件大小和最大值的比较结果 // return fileInfo.Size() >= f.maxFileSize // } func (f *FileLogger) checkTime(t int) bool { nowMinute := time.Now().Minute() return nowMinute != t } //切割文件,文件达到最大后就重新生产新的文件 func (f *FileLogger) splitFile(file *os.File) (*os.File, error) { //获取旧文件信息和生成新文件信息 nowStr := time.Now().Format("20060102150405000") fileInfo, err := file.Stat() if err != nil { fmt.Printf("get fileInfo failed,err:%v", err) return nil, err } //不能用fileLogger里的文件名,没有err //使用file查看文件名区分普通日志和错误日志 logName := path.Join(f.filePath, fileInfo.Name()) newLogName := fmt.Sprintf("%s.bak%s", logName, nowStr) // 1.关闭当前的日志文件,才能重命名 file.Close() // 2.将已满的文件重新命名,添加上时间 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("open new log file failed,err:%v", err) return nil, err } // 4.将新打开的日志文件对象赋值给 f.fileObj return fileObj, nil } //按照文件大写切割日志,每次记录日志之前都要判断下当前写的文件的大小 // func (f *FileLogger) log(lv LogLevel, format string, a ...interface{}) { // if f.enable(lv) { // msg := fmt.Sprintf(format, a...) // 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), fileName, funcName, lineNo, msg) // //记录日志等级大于Error级别,需要再err日志中再记录一遍 // if lv >= ERROR { // if f.checkSize(f.errFileObj) { // newFile, err := f.splitFile(f.errFileObj) // if err != nil { // return // } // f.errFileObj = newFile // } // fmt.Fprintf(f.errFileObj, "[%s] [%s] [%s:%s:%d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), fileName, funcName, lineNo, msg) // } // } // } //按照时间切割日志,每次记录日志之前都要当前时间 func (f *FileLogger) log(lv LogLevel, format string, a ...interface{}) { if f.enable(lv) { msg := fmt.Sprintf(format, a...) now := time.Now() funcName, fileName, lineNo := getInfo(3) //根据时间判断结果确认是否切割文件 if f.checkTime(f.timeFlag) { newFile, err := f.splitFile(f.fileObj) if err != nil { return } f.fileObj = newFile f.timeFlag = time.Now().Minute() } fmt.Fprintf(f.fileObj, "[%s] [%s] [%s:%s:%d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), fileName, funcName, lineNo, msg) //记录日志等级大于Error级别,需要再err日志中再记录一遍 if lv >= ERROR { if f.checkTime(f.timeErrFlag) { newFile, err := f.splitFile(f.errFileObj) if err != nil { return } f.errFileObj = newFile f.timeErrFlag = time.Now().Minute() } fmt.Fprintf(f.errFileObj, "[%s] [%s] [%s:%s:%d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), fileName, funcName, lineNo, msg) } } } func (f *FileLogger) Debug(format string, a ...interface{}) { f.log(DEBUG, format, a...) } func (f *FileLogger) Trace(format string, a ...interface{}) { f.log(TRACE, format, a...) } func (f *FileLogger) Info(format string, a ...interface{}) { f.log(INFO, format, a...) } func (f *FileLogger) Warning(format string, a ...interface{}) { f.log(WARNING, format, a...) } func (f *FileLogger) Error(format string, a ...interface{}) { f.log(ERROR, format, a...) } func (f *FileLogger) Fatal(format string, a ...interface{}) { f.log(FATAL, format, a...) }
-
日志库主函数
package main import ( "fmt" "os" ) func main() { fileObj, err := os.Open("./main.go") if err != nil { fmt.Printf("open file failed err:%v", err) return } fileInfo, err := fileObj.Stat() if err != nil { fmt.Printf("get fileInfo failed err:%v", err) return } fmt.Printf("文件大小是:%dB\n", fileInfo.Size()) fmt.Printf("文件名是:%s\n", fileInfo.Name()) }