加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

golang环境下的日志记录器-系列之一

发布时间:2020-12-16 18:09:21 所属栏目:大数据 来源:网络整理
导读:本小结为日志器基础组件loggor 它主要实现: 1.日志器配置信息配置(调试模式、日志类型编码、日志切分模式) 2.日志文件的建立与数据输出 输出日志格式: TIMEFORMAT LOG_TYPE BODY 2006/01/02 15:04:05 1 日志信息 下载页面 http://download.csdn.net/down

本小结为日志器基础组件loggor

它主要实现:

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(
			"%st%dt%sn",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(
			"%st%dt%sn",)
}

/**测试模式**/
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,)
	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,this._date.Format(DATEFORMAT),)
}

/**普通模式**/
func (this *Logger) SetRollingNormal(dir,"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,)
}

/**关闭日志器**/
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,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,)
	} else if this.sizeRolling {
		//大小分割模式(1,2,3....)
		suffix := int(this._suffix%int(this.maxFileCount) + 1)
		oldName = this.path
		newName = fmt.Sprint(
			this.path,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,)
	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,)
		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,-1)

	return src
}

//==================================================================测试用例
func Test() {
	logg := New()
	logg.SetType(1)
	logg.SetRollingNormal("./logs","logg")
	logg.Println("hello world!")
}


较早前版本可参见:http://studygolang.com/topics/2620

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读