Go利用原生的log实现DEBUG,INFO,WARN,ERROR日志组件

二话不说,show your code

main.go

package main
 
import (
	"fmt"
	"PrintLog/logger"
)

func main() {
	logger.SetConsole(true)//指定是否控制台打印,默认为true,true对比允许控制台打印输出
	
	//指定日志文件备份方式
	//第一个参数为日志文件存放目录
	//第二个参数为日志文件命名 命名方式如果加时间戳 具体根据需求改
	logger.SetRollingDaily("./log", "log.log")

	//指定日志级别ALL,DEBUG,INFO,WARN,ERROR级别由低到高
	//一般习惯是测试阶段为debug,生成环境为info以上
	logger.SetLevel(logger.DEBUG)

	//以下为演示效果
	str := make(chan string)
	logLevel:=[]string{"Debug","Info","Warn","Error"}

	go func() {
        for i := 0; i < 4; i = i + 1 {
            str <- logLevel[i]
        }
        close(str)
    }()

	for m := range str {
		switch  {
		case m=="Debug":
			logger.Debug("Debug:","d")
		case m=="Warn":
			logger.Warn("Warn:" ,"w")
		case m=="Info":
			logger.Info("Info:" ,"i") 
	    case m=="Error":
			logger.Error("Error:", "e")
		}
    }

	fmt.Println("main 演示行:")
 
}

logger.go

package logger

//	"log"

const (
	//go-logger version
	_VER string = "1.0.3"
)

type LEVEL int32
type UNIT int64
type _ROLLTYPE int //dailyRolling ,rollingFile

const _DATEFORMAT = "2021-08-06"

var logLevel LEVEL = 1  //

//定义数量级
const (
	_       = iota  //通过分配给空白标识符来忽略第一个值
	KB UNIT = 1 << (iota * 10) // 1 << (10*1)
	MB
	GB
	TB
)
//定义 日志级别
const (
	ALL LEVEL = iota
	DEBUG
	INFO
	WARN
	ERROR
)

const (
	_DAILY _ROLLTYPE = iota
	_ROLLFILE
)
//指定是否控制台打印,默认为true,true对比允许控制台打印输出
func SetConsole(isConsole bool) {
	defaultlog.setConsole(isConsole)
}
//设置级别
func SetLevel(_level LEVEL) {
	defaultlog.setLevel(_level)
}
//设置格式
func SetFormat(logFormat string) {
	defaultlog.setFormat(logFormat)
}
//指定日志文件备份方式为文件大小的方式
//第一个参数为日志文件存放目录
//第二个参数为日志文件命名
//第三个参数为备份文件最大数量
//第四个参数为备份文件大小
//第五个参数为文件大小的单位
//示例:logger.SetRollingFile(`C:\Users\window\Desktop`, "test.log", 10, 1, logger.KB)
func SetRollingFile(fileDir, fileName string, maxNumber int32, maxSize int64, _unit UNIT) {

	defaultlog.setRollingFile(fileDir, fileName, maxNumber, maxSize, _unit)
}
//指定日志文件备份方式为日期的方式
func SetRollingDaily(fileDir, fileName string) {

	defaultlog.setRollingDaily(fileDir, fileName)
}


//四种 日志类型
func Debug(v ...interface{}) {

	defaultlog.debug(v...)
}
func Info(v ...interface{}) {
	
	defaultlog.info(v...)
}
func Warn(v ...interface{}) {

	defaultlog.warn(v...)
}
func Error(v ...interface{}) {

	defaultlog.error(v...)
}
//设置日志类型的日志打印
//例如:logger.SetLevelFile(logger.INFO, `C:\Users\window\Desktop`, "info.log")
func SetLevelFile(level LEVEL, dir, fileName string) {
	defaultlog.setLevelFile(level, dir, fileName)
}



logw.go

package logger

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"log"
	"os"
	"runtime"
	"runtime/debug"
	"strconv"
	"sync"
	"sync/atomic"
	"time"
)

var defaultlog *logBean = getdefaultLogger()
var skip int = 4
//logBean是logger的成员
type logger struct {
	lb *logBean
}
//指定是否控制台打印,默认为true,true对比允许控制台打印输出
func (this *logger) SetConsole(isConsole bool) {
	this.lb.setConsole(isConsole)
}
//设置级别
func (this *logger) SetLevel(_level LEVEL) {
	this.lb.setLevel(_level)
}
//logger.SetFormat("=====>%s##%s")
//2021/08/07 09:45:22 main.go 22 =====>Debug##b
//设置打印格式
func (this *logger) SetFormat(logFormat string) {
	this.lb.setFormat(logFormat)
}
//指定日志文件备份方式为文件大小的方式
func (this *logger) SetRollingFile(fileDir, fileName string, maxNumber int32, maxSize int64, _unit UNIT) {
	this.lb.setRollingFile(fileDir, fileName, maxNumber, maxSize, _unit)
}
指定日志文件备份方式为日期的方式
func (this *logger) SetRollingDaily(fileDir, fileName string) {
	this.lb.setRollingDaily(fileDir, fileName)
}
//四种类型
func (this *logger) Debug(v ...interface{}) {
	this.lb.debug(v...)
}
func (this *logger) Info(v ...interface{}) {
	this.lb.info(v...)
}
func (this *logger) Warn(v ...interface{}) {
	this.lb.warn(v...)
}
func (this *logger) Error(v ...interface{}) {
	this.lb.error(v...)
}
//设置日志类型的日志打印
//例如:logger.SetLevelFile(logger.INFO, `C:\Users\window\Desktop`, "info.log")
func (this *logger) SetLevelFile(level LEVEL, dir, fileName string) {
	this.lb.setLevelFile(level, dir, fileName)
}
//log 的单元功能实现
type logBean struct {
	mu              *sync.Mutex
	logLevel        LEVEL
	maxFileSize     int64
	maxFileCount    int32
	consoleAppender bool
	rolltype        _ROLLTYPE
	format          string
	id              string
	d, i, w, e, f   string //id
}
//文件批处理的功能
type fileBeanFactory struct {
	fbs map[string]*fileBean
	mu  *sync.RWMutex
}

var fbf = &fileBeanFactory{fbs: make(map[string]*fileBean, 0), mu: new(sync.RWMutex)}
//添加新的文件
func (this *fileBeanFactory) add(dir, filename string, _suffix int, maxsize int64, maxfileCount int32) {
	this.mu.Lock()
	defer this.mu.Unlock()
	id := md5str(fmt.Sprint(dir, filename))
	if _, ok := this.fbs[id]; !ok {
		this.fbs[id] = newFileBean(dir, filename, _suffix, maxsize, maxfileCount)
	}
}
//获取文件索引
func (this *fileBeanFactory) get(id string) *fileBean {
	this.mu.RLock()
	defer this.mu.RUnlock()
	return this.fbs[id]
}
//文件的单元功能实现
type fileBean struct {
	id           string
	dir          string
	filename     string
	_suffix      int
	_date        *time.Time
	mu           *sync.RWMutex // 线程安全的方法,增加了读写锁
	logfile      *os.File
	lg           *log.Logger
	filesize     int64
	maxFileSize  int64
	maxFileCount int32
}
//返回一个logger对象
func GetLogger() (l *logger) {
	l = new(logger)
	l.lb = getdefaultLogger()
	return
}
//返回默认的logBean
func getdefaultLogger() (lb *logBean) {
	lb = &logBean{}
	lb.mu = new(sync.Mutex)
	lb.setConsole(true)
	return
}

func (this *logBean) setConsole(isConsole bool) {
	this.consoleAppender = isConsole
}

func (this *logBean) setLevelFile(level LEVEL, dir, fileName string) {
	key := md5str(fmt.Sprint(dir, fileName))
	switch level {
	case DEBUG:
		this.d = key
	case INFO:
		this.i = key
	case WARN:
		this.w = key
	case ERROR:
		this.e = key

	default:
		return
	}
	var _suffix = 0
	if this.maxFileCount < 1<<31-1 {
		for i := 1; i < int(this.maxFileCount); i++ {
			if isExist(dir + "/" + fileName + "." + strconv.Itoa(i)) {
				_suffix = i
			} else {
				break
			}
		}
	}
	fbf.add(dir, fileName, _suffix, this.maxFileSize, this.maxFileCount)
}

func (this *logBean) setLevel(_level LEVEL) {
	this.logLevel = _level
}

func (this *logBean) setFormat(logFormat string) {
	this.format = logFormat
}

func (this *logBean) setRollingFile(fileDir, fileName string, maxNumber int32, maxSize int64, _unit UNIT) {
	this.mu.Lock()
	defer this.mu.Unlock()
	if maxNumber > 0 {
		this.maxFileCount = maxNumber
	} else {
		this.maxFileCount = 1<<31 - 1
	}
	this.maxFileSize = maxSize * int64(_unit)
	this.rolltype = _ROLLFILE
	mkdirlog(fileDir)
	var _suffix = 0
	for i := 1; i < int(maxNumber); i++ {
		if isExist(fileDir + "/" + fileName + "." + strconv.Itoa(i)) {
			_suffix = i
		} else {
			break
		}
	}
	this.id = md5str(fmt.Sprint(fileDir, fileName))
	fbf.add(fileDir, fileName, _suffix, this.maxFileSize, this.maxFileCount)
}

func (this *logBean) setRollingDaily(fileDir, fileName string) {
	this.rolltype = _DAILY
	mkdirlog(fileDir)
	this.id = md5str(fmt.Sprint(fileDir, fileName))
	fbf.add(fileDir, fileName, 0, 0, 0)
}
//控制台
func (this *logBean) console(v ...interface{}) {
	s := fmt.Sprint(v...)
	if this.consoleAppender {
		_, file, line, _ := runtime.Caller(skip)
		short := file
		for i := len(file) - 1; i > 0; i-- {
			if file[i] == '/' {
				short = file[i+1:]
				break
			}
		}
		file = short
		if this.format == "" {
			log.Println(file, strconv.Itoa(line), s)
		} else {
			vs := make([]interface{}, 0)
			vs = append(vs, file)
			vs = append(vs, strconv.Itoa(line))
			for _, vv := range v {
				vs = append(vs, vv)
			}
			log.Printf(fmt.Sprint("%s %s ", this.format, "\n"), vs...)
		}
	}
}
//日志
func (this *logBean) log(level string, v ...interface{}) {
	defer catchError()
	s := fmt.Sprint(v...)
	length := len([]byte(s))
	var lg *fileBean = fbf.get(this.id)
	var _level = ALL
	switch level {
	case "debug":
		if this.d != "" {
			lg = fbf.get(this.d)
		}
		_level = DEBUG
	case "info":
		if this.i != "" {
			lg = fbf.get(this.i)
		}
		_level = INFO
	case "warn":
		if this.w != "" {
			lg = fbf.get(this.w)
		}
		_level = WARN
	case "error":
		if this.e != "" {
			lg = fbf.get(this.e)
		}
		_level = ERROR

	}
	if lg != nil {
		this.fileCheck(lg)
		lg.addsize(int64(length))
		if this.logLevel <= _level {
			if lg != nil {
				if this.format == "" {
					lg.write(level, s)
				} else {
					lg.writef(this.format, v...)
				}
			}
			this.console(v...)
		}
	} else {
		this.console(v...)
	}
}
//debug类型
func (this *logBean) debug(v ...interface{}) {
	this.log("debug", v...)
}
//info类型
func (this *logBean) info(v ...interface{}) {
	this.log("info", v...)
}
//warn类型
func (this *logBean) warn(v ...interface{}) {
	this.log("warn", v...)
}
//error类型
func (this *logBean) error(v ...interface{}) {
	this.log("error", v...)
}

//检查文件是否需要重命名
func (this *logBean) fileCheck(fb *fileBean) {
	defer catchError()
	if this.isMustRename(fb) {
		this.mu.Lock()
		defer this.mu.Unlock()
		if this.isMustRename(fb) {
			fb.rename(this.rolltype)
		}
	}
}

//--------------------------------------------------------------------------------
//是否重命名
func (this *logBean) isMustRename(fb *fileBean) bool {
	switch this.rolltype {
	case _DAILY:
		t, _ := time.Parse(_DATEFORMAT, time.Now().Format(_DATEFORMAT))
		if t.After(*fb._date) {
			return true
		}
	case _ROLLFILE:
		return fb.isOverSize()
	}
	return false
}
//找出 下一个后缀
func (this *fileBean) nextSuffix() int {
	return int(this._suffix%int(this.maxFileCount) + 1)
}
//新文件生成
func newFileBean(fileDir, fileName string, _suffix int, maxSize int64, maxfileCount int32) (fb *fileBean) {
	t, _ := time.Parse(_DATEFORMAT, time.Now().Format(_DATEFORMAT))
	fb = &fileBean{dir: fileDir, filename: fileName, _date: &t, mu: new(sync.RWMutex)}
	fb.logfile, _ = os.OpenFile(fileDir+"/"+fileName, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
	fb.lg = log.New(fb.logfile, "", log.Ldate|log.Ltime|log.Lshortfile)
	fb._suffix = _suffix
	fb.maxFileSize = maxSize
	fb.maxFileCount = maxfileCount
	fb.filesize = fileSize(fileDir + "/" + fileName)
	fb._date = &t
	return
}
//重命名
func (this *fileBean) rename(rolltype _ROLLTYPE) {
	this.mu.Lock()
	defer this.mu.Unlock()
	this.close()
	nextfilename := ""
	switch rolltype {
	case _DAILY:
		nextfilename = fmt.Sprint(this.dir, "/", this.filename, ".", this._date.Format(_DATEFORMAT))
	case _ROLLFILE:
		nextfilename = fmt.Sprint(this.dir, "/", this.filename, ".", this.nextSuffix())
		this._suffix = this.nextSuffix()
	}
	if isExist(nextfilename) {
		os.Remove(nextfilename)
	}
	os.Rename(this.dir+"/"+this.filename, nextfilename)
	t, _ := time.Parse(_DATEFORMAT, time.Now().Format(_DATEFORMAT))
	this._date = &t
	this.logfile, _ = os.OpenFile(this.dir+"/"+this.filename, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
	this.lg = log.New(this.logfile, "", log.Ldate|log.Ltime|log.Lshortfile)
	this.filesize = fileSize(this.dir + "/" + this.filename)
}
//将size加到&this.filesize,
func (this *fileBean) addsize(size int64) {
	atomic.AddInt64(&this.filesize, size)
}
//文件格式format为空时 写文件的方式
func (this *fileBean) write(level string, v ...interface{}) {
	this.mu.RLock()
	defer this.mu.RUnlock()
	s := fmt.Sprint(v...)
	this.lg.Output(skip+1, fmt.Sprintln(level, s))
}
//文件格式format不为空时 写文件的方式
func (this *fileBean) writef(format string, v ...interface{}) {
	this.mu.RLock()
	defer this.mu.RUnlock()
	this.lg.Output(skip+1, fmt.Sprintf(format, v...))
}
//是否超过大小
func (this *fileBean) isOverSize() bool {
	return this.filesize >= this.maxFileSize
}
//关闭文件
func (this *fileBean) close() {
	this.logfile.Close()
}


//创建log文件目录
func mkdirlog(dir string) (e error) {
	_, er := os.Stat(dir)
	b := er == nil || os.IsExist(er)
	if !b {
		if err := os.MkdirAll(dir, 0666); err != nil {
			if os.IsPermission(err) {
				e = err
			}
		}
	}
	return
}
//文件大小
func fileSize(file string) int64 {
	f, e := os.Stat(file)
	if e != nil {
		fmt.Println(e.Error())
		return 0
	}
	return f.Size()
}
//路径是否存在
func isExist(path string) bool {
	_, err := os.Stat(path)
	return err == nil || os.IsExist(err)
}
//[]byte -> String(16进制)
//字符串来表示16进制
func md5str(s string) string {
	m := md5.New()
	m.Write([]byte(s))
	return hex.EncodeToString(m.Sum(nil))
}
//捕获异常
func catchError() {
	if err := recover(); err != nil {
		fmt.Println(string(debug.Stack()))
	}
}

文件关系图

在这里插入图片描述

注:

PrintLog是项目文件夹名,log是日志文件夹,logger是logger.go和logw.go放置位置,main.go是主程序的源码,开发环境是VSCode+Go,其余文件是环境生成,各位可以自行操作~~~~~~~~

标题日志输出效果

2021/08/07 10:30:21 main.go:22: debug Debugb
2021/08/07 10:30:21 main.go:23: info Infoi
2021/08/07 10:30:21 main.go:24: warn Warnw
2021/08/07 10:30:21 main.go:25: error Errore
2021/08/07 10:30:27 main.go:22: debug Debugb
2021/08/07 10:30:27 main.go:23: info Infoi
2021/08/07 10:30:27 main.go:24: warn Warnw
2021/08/07 10:30:27 main.go:25: error Errore
2021/08/07 10:30:42 main.go:22: debug Debugb
2021/08/07 10:30:42 main.go:23: info Infoi
2021/08/07 10:30:42 main.go:24: warn Warnw
2021/08/07 10:30:42 main.go:25: error Errore
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值