您的位置:首页 > 编程语言 > Go语言

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
点击打开链接

//@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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息