golang环境下的日志记录器-系列之一
2017-07-18 10:29
507 查看
本小结为日志器基础组件loggor
它主要实现:
1.日志器配置信息配置(调试模式、日志类型编码、日志切分模式)
2.日志文件的建立与数据输出
输出日志格式:
TIMEFORMAT LOG_TYPE BODY
2006/01/02 15:04:05 1日志信息
下载页面
http://download.csdn.net/download/hansewolf/9902133
点击打开链接
较早前版本可参见:http://studygolang.com/topics/2620
它主要实现:
1.日志器配置信息配置(调试模式、日志类型编码、日志切分模式)
2.日志文件的建立与数据输出
输出日志格式:
TIMEFORMAT LOG_TYPE BODY
2006/01/02 15:04:05 1日志信息
下载页面
http://download.csdn.net/download/hansewolf/9902133
点击打开链接
//@description 日志记录工具类 /* 日志格式: 时间(系统时间) 日志类型(方法设置) 日志内容(动态输入) 日志类包含两个同步锁: 缓冲区锁-mu_buf 文件锁-mu_file 日志输入操作 Printf Println 1.获取缓冲区锁 2.写入缓冲区 3.释放缓冲区锁 4.A.调用bufWrite,B.等待定时调用bufWrite 日志输出操作 bufWrite 1.获取文件锁 2.判断缓冲区,不需写入则返回 3.获取缓冲区锁 4.写入缓冲区 5.释放缓冲区锁 日志监听操作 fileMonitor A.文件监听定时器到期fileCheck 1.判断是否需要文件重名,并后续操作 1.1.获取文件锁 1.2.再次判断文件是否需要重名 1.3.重命名文件 1.4.释放文件锁 B.定时写入定时器到期bufWrite 文件定时写入bufWrite与文件监听fileMonitor时间间隔 t1,t2 防止文件碰撞(秒为单位时)需要满足 (n-1)t1%60 != (n-1)t2%60 顺序获取锁:缓冲锁-->文件锁 */ //@author chenbintao //@data 2016-08-04 17:47 调试代码无报错 // 2017-07-07 18:59 优化定时检测线程计算公式 package loggor import ( "bytes" "encoding/json" "fmt" "io" "log" "math" "os" "path" "runtime/debug" "strings" "sync" "time" ) const ( _VERSION_ = "1.0.1" //版本 DATEFORMAT = "2006-01-02" //日期格式(用于文件命名) TIMEFORMAT = "2006/01/02 15:04:05" //时间格式(日志时间格式) _SPACE = " " //参数分割 _TABLE = "\t" //日志文件行分隔符 _JOIN = "&" //参数连接符 _FILE_OPERAT_MODE_ = 0644 //文件操作权限模式 _FILE_CREAT_MODE_ = 0666 //文件建立权限模式 _LABEL_ = "[_loggor_]" //标签 ) const ( //日志文件存储模式 LOG_FILE_SAVE_USUAL = 1 //普通模式,不分割 LOG_FILE_SAVE_SIZE = 2 //大小分割 LOG_FILE_SAVE_DATE = 3 //日期分割 ) const ( //文件大小单位 _ = iota KB int64 = 1 << (iota * 10) MB GB TB ) const ( _EXTEN_NAME_ = ".log" //日志文件后缀名 _CHECK_TIME_ time.Duration = 900 * time.Millisecond //定时检测是否分割检测周期 _WRITE_TIME_ time.Duration = 1300 * time.Millisecond //定时写入文件周期 ) var ( IS_DEBUG = false //调试模式 TIMEER_WRITE = false //定时写入文件 ) type LOGGER interface { SetDebug(bool) //设置日志文件路径及名称 SetType(uint) //设置日志类型 SetRollingFile(string, string, int32, int64, int64) //按照文件大小分割 SetRollingDaily(string, string) //按照日期分割 SetRollingNormal(string, string) //设置普通模式 Close() //关闭 Println(a ...interface{}) //打印日志 Printf(format string, a ...interface{}) //格式化输出 } //==================================================================日志记录器 type Logger struct { log_type uint //日志类型 path string //日志文件路径 dir string //目录 filename string //文件名 maxFileSize int64 //文件大小 maxFileCount int32 //文件个数 dailyRolling bool //日分割 sizeRolling bool //大小分割 nomalRolling bool //普通模式(不分割) _suffix int //大小分割文件的当前序号 _date *time.Time //文件时间 mu_buf *sync.Mutex //缓冲锁 mu_file *sync.Mutex //文件锁 logfile *os.File //文件句柄 timer *time.Timer //监视定时器 writeTimer *time.Timer //批量写入定时器 buf *bytes.Buffer //缓冲区(公用buf保证数据写入的顺序性) } /**获取日志对象**/ func New() *Logger { this := &Logger{} this.buf = &bytes.Buffer{} this.mu_buf = new(sync.Mutex) this.mu_file = new(sync.Mutex) return this } /**格式行输出**/ func (this *Logger) Printf(format string, a ...interface{}) { defer func() { if !TIMEER_WRITE { this.bufWrite() } }() tp := fmt.Sprintf(format, a...) //tp = ToLineString(tp) this.mu_buf.Lock() defer this.mu_buf.Unlock() this.buf.WriteString( fmt.Sprintf( "%s\t%d\t%s\n", time.Now().Format(TIMEFORMAT), this.log_type, tp, ), ) } /**逐行输出**/ func (this *Logger) Println(a ...interface{}) { defer func() { if !TIMEER_WRITE { this.bufWrite() } }() tp := fmt.Sprint(a...) //tp = ToLineString(tp) this.mu_buf.Lock() defer this.mu_buf.Unlock() this.buf.WriteString( fmt.Sprintf( "%s\t%d\t%s\n", time.Now().Format(TIMEFORMAT), this.log_type, tp, ), ) } /**测试模式**/ func (this *Logger) SetDebug(is_debug bool) { IS_DEBUG = is_debug } /**定时写入**/ func (this *Logger) SetTimeWrite(time_write bool) *Logger { TIMEER_WRITE = time_write return this } /**日志类型**/ func (this *Logger) SetType(tp uint) { this.log_type = tp } /**大小分割**/ func (this *Logger) SetRollingFile(dir, _file string, maxn int32, maxs int64, _u int64) { //0.输入合法性 if this.sizeRolling || this.dailyRolling || this.nomalRolling { log.Println(_LABEL_, "mode can't be changed!") return } //1.设置各模式标志符 this.sizeRolling = true this.dailyRolling = false this.nomalRolling = false //2.设置日志器各参数 this.maxFileCount = maxn this.maxFileSize = maxs * int64(_u) this.dir = dir this.filename = _file for i := 1; i <= int(maxn); i++ { sizeFile := fmt.Sprintf( dir, _file, _EXTEN_NAME_, ".", fmt.Sprintf("%05d", i), ) if isExist(sizeFile) { this._suffix = i } else { break } } //3.实时文件写入 this.path = fmt.Sprint( dir, _file, _EXTEN_NAME_, ) this.startLogger(this.path) } /**日期分割**/ func (this *Logger) SetRollingDaily(dir, _file string) { //0.输入合法性 if this.sizeRolling || this.dailyRolling || this.nomalRolling { log.Println(_LABEL_, "mode can't be changed!") return } //1.设置各模式标志符 this.sizeRolling = false this.dailyRolling = true this.nomalRolling = false //2.设置日志器各参数 this.dir = dir this.filename = _file this._date = getNowFormDate(DATEFORMAT) this.startLogger( fmt.Sprint( this.dir, this.filename, _EXTEN_NAME_, ".", this._date.Format(DATEFORMAT), ), ) } /**普通模式**/ func (this *Logger) SetRollingNormal(dir, _file string) { //0.输入合法性 if this.sizeRolling || this.dailyRolling || this.nomalRolling { log.Println(_LABEL_, "mode can't be changed!") return } //1.设置各模式标志符 this.sizeRolling = false this.dailyRolling = false this.nomalRolling = true //2.设置日志器各参数 this.dir = dir this.filename = _file this.startLogger( fmt.Sprint( dir, _file, _EXTEN_NAME_, ), ) } /**关闭日志器**/ func (this *Logger) Close() { //0.获取锁 this.mu_buf.Lock() defer this.mu_buf.Unlock() this.mu_file.Lock() defer this.mu_file.Unlock() //1.关闭 if nil != this.timer { this.timer.Stop() } if nil != this.writeTimer { this.writeTimer.Stop() } if this.logfile != nil { err := this.logfile.Close() if err != nil { log.Println(_LABEL_, "file close err", err) } } else { log.Println(_LABEL_, "file has been closed!") } //2.清理 this.sizeRolling = false this.dailyRolling = false this.nomalRolling = false } //==================================================================内部工具方法 //初始日志记录器(各日志器统一调用) func (this *Logger) startLogger(tp string) { defer func() { if e, ok := recover().(error); ok { log.Println(_LABEL_, "WARN: panic - %v", e) log.Println(_LABEL_, string(debug.Stack())) } }() //1.初始化空间 var err error this.buf = &bytes.Buffer{} this.mu_buf = new(sync.Mutex) this.mu_file = new(sync.Mutex) this.path = tp checkFileDir(tp) this.logfile, err = os.OpenFile( tp, os.O_RDWR|os.O_APPEND|os.O_CREATE, _FILE_OPERAT_MODE_, ) if nil != err { log.Println(_LABEL_, "OpenFile err!") } //2.开启监控线程 go func() { this.timer = time.NewTimer(_CHECK_TIME_) this.writeTimer = time.NewTimer(_WRITE_TIME_) if !TIMEER_WRITE { this.writeTimer.Stop() } for { select { //定时检测是否分割 case <-this.timer.C: this.fileCheck() if IS_DEBUG && false { log.Printf("*") //心跳 } break //定时写入文件(定时写入,会导致延时) case <-this.writeTimer.C: this.bufWrite() if IS_DEBUG && false { log.Printf(".") //心跳 } break } } }() if IS_DEBUG { jstr, err := json.Marshal(this) if nil == err { log.Println(_LABEL_, _VERSION_, string(jstr)) } } } //文件检测(会锁定文件) func (this *Logger) fileCheck() { //0.边界判断 if nil == this.mu_file || nil == this.logfile || "" == this.path { return } defer func() { if e, ok := recover().(error); ok { log.Println(_LABEL_, "WARN: panic - %v", e) log.Println(_LABEL_, string(debug.Stack())) } }() //1.重命名判断 var RENAME_FLAG bool = false var CHECK_TIME time.Duration = _CHECK_TIME_ this.timer.Stop() defer this.timer.Reset(CHECK_TIME) if this.dailyRolling { //日分割模式 now := getNowFormDate(DATEFORMAT) if nil != now && nil != this._date && now.After(*this._date) { //超时重名 RENAME_FLAG = true } else { //检测间隔动态调整(1/60分割时间差值+基准检测时长) du := this._date.UnixNano() - now.UnixNano() abs := math.Abs(float64(du)) CHECK_TIME += time.Duration(abs / 60) } } else if this.sizeRolling { //文件大小模式 fileSize := fileSize(this.path) if "" != this.path && this.maxFileCount >= 1 && fileSize >= this.maxFileSize { //超量重名 RENAME_FLAG = true } else { //检测时长(假设磁盘写入带宽100M/S) du := fileSize - this.maxFileSize abs := math.Abs(float64(du)) CHECK_TIME += time.Duration(((uint64(abs) >> 2 * 10) / 100)) * time.Second } } else if this.nomalRolling { //普通模式 RENAME_FLAG = false } //2.重名操作 if RENAME_FLAG { this.mu_file.Lock() defer this.mu_file.Unlock() if IS_DEBUG { log.Println(_LABEL_, this.path, "is need rename.") } this.fileRename() } return } //重命名文件 func (this *Logger) fileRename() { //1.生成文件名称 var err error var newName string var oldName string defer func() { if IS_DEBUG { log.Println( _LABEL_, oldName, "->", newName, ":", err, ) } }() if this.dailyRolling { //日期分割模式(文件不重命名) oldName = this.path newName = this.path this._date = getNowFormDate(DATEFORMAT) this.path = fmt.Sprint( this.dir, this.filename, _EXTEN_NAME_, ".", this._date.Format(DATEFORMAT), ) } else if this.sizeRolling { //大小分割模式(1,2,3....) suffix := int(this._suffix%int(this.maxFileCount) + 1) oldName = this.path newName = fmt.Sprint( this.path, ".", fmt.Sprintf("%05d", suffix), ) this._suffix = suffix this.path = this.path } else if this.nomalRolling { //常规模式 } //2.处理旧文件 this.logfile.Close() if "" != oldName && "" != newName && oldName != newName { if isExist(newName) { //删除旧文件 err := os.Remove(newName) if nil != err { log.Println(_LABEL_, "remove file err", err.Error()) } } err = os.Rename(oldName, newName) if err != nil { //重名旧文件 log.Println(_LABEL_, "rename file err", err.Error()) } } //3.创建新文件 this.logfile, err = os.OpenFile( this.path, os.O_RDWR|os.O_APPEND|os.O_CREATE, _FILE_OPERAT_MODE_, ) if err != nil { log.Println(_LABEL_, "creat file err", err.Error()) } return } //缓冲写入文件 func (this *Logger) bufWrite() { //0.边界处理 if nil == this.buf || "" == this.path || nil == this.logfile || nil == this.mu_buf || nil == this.mu_file || this.buf.Len() <= 0 { return } //1.数据写入 var WRITE_TIME time.Duration = _WRITE_TIME_ if nil != this.writeTimer { this.writeTimer.Stop() defer this.writeTimer.Reset(WRITE_TIME) } this.mu_file.Lock() defer this.mu_file.Unlock() this.mu_buf.Lock() defer this.mu_buf.Unlock() defer this.buf.Reset() n, err := io.WriteString(this.logfile, this.buf.String()) if nil != err { //写入失败,校验文件,不存在则创建 checkFileDir(this.path) this.logfile, err = os.OpenFile( this.path, os.O_RDWR|os.O_APPEND|os.O_CREATE, _FILE_OPERAT_MODE_, ) if nil != err { log.Println(_LABEL_, "log bufWrite() err!") } } //根据缓冲压力进行动态设置写入间隔 if n == 0 { WRITE_TIME = _WRITE_TIME_ } else { WRITE_TIME = WRITE_TIME * time.Duration(n/n) } } //==================================================================辅助方法 //获取文件大小 func fileSize(file string) int64 { this, e := os.Stat(file) if e != nil { if IS_DEBUG { log.Println(_LABEL_, e.Error()) } return 0 } return this.Size() } //判断路径是否存在 func isExist(path string) bool { _, err := os.Stat(path) return err == nil || os.IsExist(err) } //检查文件路径文件夹,不存在则创建 func checkFileDir(tp string) { p, _ := path.Split(tp) d, err := os.Stat(p) if err != nil || !d.IsDir() { if err := os.MkdirAll(p, _FILE_CREAT_MODE_); err != nil { log.Println(_LABEL_, "CheckFileDir() Creat dir faile!") } } } //获取当前指定格式的日期 func getNowFormDate(form string) *time.Time { t, err := time.Parse(form, time.Now().Format(form)) if nil != err { log.Println(_LABEL_, "getNowFormDate()", err.Error()) t = time.Time{} return &t } return &t } //字符串安全转义 func ToLineString(src string) string { src = strings.Replace(src, "\n", "\\n", -1) src = strings.Replace(src, "\r", "\\r", -1) return src } //字符串安全反转义 func FromLineString(src string) string { src = strings.Replace(src, "\\n", "\n", -1) src = strings.Replace(src, "\r", "\\r", -1) return src } //==================================================================测试用例 func Test() { logg := New() logg.SetType(1) logg.SetRollingNormal("./logs", "logg") logg.Println("hello world!") }
较早前版本可参见:http://studygolang.com/topics/2620
相关文章推荐
- golang环境下的日志记录器-系列之二
- golang环境下的日志记录器-系列之三
- 【GoLang】GO语言系列--001.GO开发环境搭建
- Windows环境下Android Studio系列—日志调试
- iBatisnet系列(二) 配置运行环境和日志处理
- Golang 入门系列(一)Go环境搭建
- iBatisnet系列(二) 配置运行环境和日志处理
- 【PHP系列】PHP推荐标准之PSR-3,日志记录器接口
- 日志库EasyLogging++学习系列(6)—— 日志记录器
- Windows环境下Android Studio系列5—日志调试
- iBatisnet系列(二) 配置运行环境和日志处理
- iBatisnet系列(二) 配置运行环境和日志处理
- 日志库EasyLogging++学习系列(6)—— 日志记录器
- iBatisnet系列(二) 配置运行环境和日志处理
- 基于DragonBoard 410c的Grove Starter Kit体验系列之环境构建
- 服务器环境搭建系列(三)-JDK篇
- 1-5.将Hadoop添加到环境变量,初始化HDFS,启动Hadoop,测试hdfs(Hadoop系列day01)
- RF+Appium框架自动化测试系列一之(Mac下Appium环境搭建)万事开头难
- 设计模式:日志记录器——工厂方法模式
- Jenkins 系列: (七) Jenkins 环境变量管理