
实现异步的日志库输出
有专门的一个日志文件收集全部的错误日志
发布日期:2021-05-06 23:58:20
浏览次数:27
分类:精选文章
本文共 5560 字,大约阅读时间需要 18 分钟。
需求分析:
1.支持往不同的地方输出日志(终端还是指定的文件)
2.日志分级别(DEBUG,WANRING,ERROR…)
3.支持开关控制,能设置某级别以上日志才输出
4.完整的日志要有时间,行号,文件名,日志级别,日志信息。
5.日志文件要切割(按文件大小或者日期切割)
func main() { logger := logfile.NewFileLogger("DEBUG","D:/","guoyilin",1*1024) name := "郭10" for { logger.Debug("DEBUG日志 %s",name) logger.Warning("Warning日志") logger.Error("Error日志") logger.Panic("Panic日志") time.Sleep(time.Second) }}
type LogLevel uint16//底层的几种日志级别都是int类型const ( Unknown LogLevel = iota Debug Warning Error Panic)type FileLogger struct { level LogLevel filePath string fileName string maxFileSize int64 fileObj *os.File //用作写文件时的os.File对象 fileErrObj *os.File logChan chan *logMsg //用作将来把日志的全部信息传入通道,再由后台goroutine从通道读出来}// 放到通道中的日志全部信息, 传入通道时只需传入指针,不需要传整个结构体(数据太大,并且不好控制数据类型)type logMsg struct { level LogLevel logMessage string fileName string funcName string timeStamp string line int}//main方法中,传入的参数是debug,warning等字符串,需要转型到我们自定义的 日志级别类型func ParseLeverStTOLogLevel(level string) LogLevel { s:=strings.ToLower(level) switch s { case "debug": return Debug case "warning": return Warning case "error": return Error case "panic": return Panic default: return Unknown }}//构造方法,生成logger对象func NewFileLogger (logLevelStr ,filePath ,fileName string ,maxFileSize int64) *FileLogger { loglevel :=ParseLeverStTOLogLevel(logLevelStr) f:= &FileLogger{ level: loglevel, filePath: filePath, fileName: fileName, maxFileSize: maxFileSize, logChan: make(chan *logMsg,50000), //注意channel是引用类型,必须初始化,否则会报空指针 } err :=f.InitFileObj() if err!=nil { panic(err) } return f}//初始化fileObj 和用于错误级别日志的fileErrObj对象func (f *FileLogger)InitFileObj () error{ //path.join自动组合带有反斜线的 文件名称 fullName := path.Join(f.filePath,f.fileName) fmt.Printf("打印path.join方法 fullname值: %s \n",fullName) fileObj ,err :=os.OpenFile(fullName,os.O_APPEND|os.O_WRONLY|os.O_CREATE ,0644) if err != nil{ fmt.Println("OpenFile failed ") return err } errObj ,err :=os.OpenFile(fullName+"err.txt",os.O_APPEND|os.O_WRONLY|os.O_CREATE ,0644) if err != nil{ fmt.Println("ErrFile failed ") return err } //日志文件打开后 进行对象赋值 f.fileObj = fileObj f.fileErrObj =errObj //defer fileObj.Close() //defer errObj.Close() return nil}//获取logger对象调用时,方法名,行号func getInfo(skip int) (fileName,funcName string ,line int) { pc,fileName,line ,ok:=runtime.Caller(skip) if !ok{ fmt.Println("runtime ----error") return } funcName =runtime.FuncForPC(pc).Name() //取当前路径, 否则默认fileName为绝对路径 //fileName = path.Base(file) return fileName,funcName,line}//后台gorountine 从通道中取值,并且写入文件func (f *FileLogger) WriteBackGround() { for { //先判断是否超过单个日志文件的最大内存,大于的话就分割,重命名一个新文件 if f.checkSize(f.fileObj) { f.splitFile() } logTmp := <- f.logChan _, error := fmt.Fprintf(f.fileObj,"[%v ][%s] [%s %s 行号:%v ]%s \n", logTmp.level, logTmp.timeStamp, logTmp.fileName, logTmp.funcName, logTmp.line, logTmp.logMessage) if error != nil { fmt.Println(error) } //设置大于Error级别的日志,输出到专门的文档 if logTmp.level >= Debug { fmt.Fprintf(f.fileErrObj,"[%v ][%s] [%s %s 行号:%v ]%s \n", logTmp.level, logTmp.timeStamp, logTmp.fileName, logTmp.funcName, logTmp.line, logTmp.logMessage) } }}func (f *FileLogger)log (lever LogLevel ,msg string,a...interface{ }) { if f.enable(lever) { //拼接成字符串信息 LogMessage := fmt.Sprintf(msg, a...) //logRange := ParseLeverIntToString(lever) now := time.Now() fileName, funcName, line := getInfo(3) //把日志发送到通道中 logTmp := &logMsg{ level: lever, logMessage: LogMessage, fileName: fileName, funcName: funcName, timeStamp: now.Format("2006-01-02 15:04:05"), line: line, } select { case f.logChan <- logTmp: for i:=0;i<10 ;i++ { go f.WriteBackGround() } default: } }}//检查单个日志文件是否超出 默认的设置大小func (f *FileLogger)checkSize(file *os.File) bool { fileInfo,err :=file.Stat() fmt.Println(fileInfo.Size()) if err != nil { fmt.Printf("get file info failed err: %s" ,err) return false } return fileInfo.Size() >=f.maxFileSize}//切割文件,传入一个新的OpenFile参数路径,返回一个新的FileObj,并赋值给 f.fileObj, 用于接下来的写文件func (f *FileLogger)splitFile () { now := time.Now() nowStr := now.Format("2006-01-02-15040500.txt") OldFileName := path.Join(f.filePath ,f.fileName) NewFilename := OldFileName+nowStr fmt.Println(NewFilename) //3.打开一个新的日志文件 fileNewObj,err := os.OpenFile(NewFilename,os.O_CREATE|os.O_WRONLY|os.O_APPEND ,0644) if err != nil { fmt.Printf("log 方法中 180行 出错 err信息 := %s" ,err) } f.fileObj = fileNewObj}// 控制日志级别输出func (f *FileLogger)enable(level LogLevel) bool{ return level >= f.level}// a... interface{} 方便以后Debug的时候,可以加入任意的信息,比如error信息。func (f *FileLogger)Debug (msg string ,a... interface{ }) { if f.enable(Debug){ f.log(Debug,msg,a...) }}func (f *FileLogger)Warning (msg string ,a... interface{ }) { if f.enable(Warning){ f.log(Warning,msg,a...) }}func (f *FileLogger)Error (msg string ,a... interface{ }) { if f.enable(Error){ f.log(Error,msg,a...) }}func (f *FileLogger)Panic (msg string ,a... interface{ }) { if f.enable(Panic){ f.log(Panic,msg,a...) }}
成果展示:

可以控制Error级别日志以上才输出(比如生产环境上不需要展示DEBUG日志)


发表评论
最新留言
关注你微信了!
[***.104.42.241]2025年04月15日 19时04分25秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
entity framework core在独立类库下执行迁移操作
2021-05-09
Asp.Net Core 2.1+的视图缓存(响应缓存)
2021-05-09
【wp】HWS计划2021硬件安全冬令营线上选拔赛
2021-05-09
Ef+T4模板实现代码快速生成器
2021-05-09
JQuery选择器
2021-05-09
多线程之volatile关键字
2021-05-09
2.2.2原码补码移码的作用
2021-05-09
Java面试题:Servlet是线程安全的吗?
2021-05-09
Java集合总结系列2:Collection接口
2021-05-09
Linux学习总结(九)—— CentOS常用软件安装:中文输入法、Chrome
2021-05-09
比技术还重要的事
2021-05-09
linux线程调度策略
2021-05-09
软中断和实时性
2021-05-09
Linux探测工具BCC(可观测性)
2021-05-09
SNMP介绍及使用,超有用,建议收藏!
2021-05-09
HDU5589:Tree(莫队+01字典树)
2021-05-09
不停机替换线上代码? 你没听错,Arthas它能做到
2021-05-09
Python开发之序列化与反序列化:pickle、json模块使用详解
2021-05-09
采坑 - 字符串的 "" 与 pd.isnull()
2021-05-09
无序列表 - 链表
2021-05-09