支持日志切分的日志记录器(日期、大小、普通)-博文

hansegod · · 4134 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

//@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 hanse
//@data 2016-08-04	13:39	调试代码无报错
//		2017-04-08	17:04	修改bufWrite获取锁机制,写入方式采用线程实现,增加必要的设计说明
//		2017-05-24	21:11	代码结构调整,合并非不需方法,对检测及写入事件进行动态设置,并对定时器进行重置

package loggor

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"math"
	"os"
	"path"
	"runtime/debug"
	"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 {
			go this.bufWrite()
		}
	}()

	tp := fmt.Sprintf(format, a...)
	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 {
			go this.bufWrite()
		}
	}()

	tp := fmt.Sprint(a...)
	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 {
			//检测间隔动态调整
			du := this._date.UnixNano() - now.UnixNano()
			abs := math.Abs(float64(du))
			CHECK_TIME = CHECK_TIME * time.Duration(abs/abs)
		}
	} else if this.sizeRolling {
		//文件大小模式
		if "" != this.path &&
			this.maxFileCount >= 1 &&
			fileSize(this.path) >= this.maxFileSize {
			//超量重名
			RENAME_FLAG = true
		}
	} 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 Test() {
	logg := New()
	logg.SetType(1)
	logg.SetRollingNormal("./logs", "logg")
	logg.Println("hello world!")
}

简单的使用用例:

var synLoger Logger = Logger{} synLoger.SetType(config.LOG_TYPE_UNKNOW) synLoger.SetRollingNormal(path.Split(config.LOG_PATH_ROOT + "/" + config.LOG_PATH_SYNCER))


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

4134 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传