二话不说,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