Go: how to use native log to implement DEBUG, INFO, WARN, ERROR log components

created at 08-08-2021 views: 1

Don't talk nonsense, show you my code

main.go

package main

import (
    "fmt"
    "PrintLog/logger"
)

func main() {
    logger.SetConsole(true)//Specify whether to print to the console, the default is true, true contrast allows the console to print output

    //Specify the log file backup method
    //The first parameter is the log file storage directory
    //The second parameter is the name of the log file. If the naming method is time-stamped, it can be changed according to the requirements.
    logger.SetRollingDaily("./log", "log.log")

    //Specify the log level ALL, DEBUG, INFO, WARN, ERROR level from low to high
    //The general habit is to debug in the test phase, and the generation environment is above info
    logger.SetLevel(logger.DEBUG)

    //The following is a demonstration effect
    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 demo line:")

}

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  //

//Define the order of magnitude
const (
    _       = iota  //Ignore the first value by assigning it to a blank identifier
    KB UNIT = 1 << (iota * 10) // 1 << (10*1)
    MB
    GB
    TB
)
//Define the log level
const (
    ALL LEVEL = iota
    DEBUG
    INFO
    WARN
    ERROR
)

const (
    _DAILY _ROLLTYPE = iota
    _ROLLFILE
)
//Specify whether to print to the console, the default is true, true contrast allows the console to print output
func SetConsole(isConsole bool) {
    defaultlog.setConsole(isConsole)
}
//Set level
func SetLevel(_level LEVEL) {
    defaultlog.setLevel(_level)
}
//Set format
func SetFormat(logFormat string) {
    defaultlog.setFormat(logFormat)
}
//Specify the log file backup method as the file size method
//The first parameter is the log file storage directory
//The second parameter is the name of the log file
//The third parameter is the maximum number of backup files
//The fourth parameter is the size of the backup file
//The fifth parameter is the unit of file size
//Example: 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)
}
//Specify the log file backup method as the date method
func SetRollingDaily(fileDir, fileName string) {

    defaultlog.setRollingDaily(fileDir, fileName)
}


//Four types of logs
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...)
}
//Set the log printing of the log type
//For example: 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 is a member of logger
type logger struct {
    lb *logBean
}
//Specify whether to print to the console, the default is true, true contrast allows the console to print output
func (this *logger) SetConsole(isConsole bool) {
    this.lb.setConsole(isConsole)
}
//Set level
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
//Set print format
func (this *logger) SetFormat(logFormat string) {
    this.lb.setFormat(logFormat)
}
//Specify the log file backup method as the file size method
func (this *logger) SetRollingFile(fileDir, fileName string, maxNumber int32, maxSize int64, _unit UNIT) {
    this.lb.setRollingFile(fileDir, fileName, maxNumber, maxSize, _unit)
}
//Specify the log file backup method as the date method
func (this *logger) SetRollingDaily(fileDir, fileName string) {
    this.lb.setRollingDaily(fileDir, fileName)
}
//Four types
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...)
}
//Set the log printing of the log type
//For example: 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 unit function realization
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
}
//File batch processing function
type fileBeanFactory struct {
    fbs map[string]*fileBean
    mu  *sync.RWMutex
}

var fbf = &fileBeanFactory{fbs: make(map[string]*fileBean, 0), mu: new(sync.RWMutex)}
//Add new file
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)
    }
}
//Get file index
func (this *fileBeanFactory) get(id string) *fileBean {
    this.mu.RLock()
    defer this.mu.RUnlock()
    return this.fbs[id]
}
//File unit function realization
type fileBean struct {
    id           string
    dir          string
    filename     string
    _suffix      int
    _date        *time.Time
    mu           *sync.RWMutex //Thread-safe method, adding read-write lock
    logfile      *os.File
    lg           *log.Logger
    filesize     int64
    maxFileSize  int64
    maxFileCount int32
}
//Return a logger object
func GetLogger() (l *logger) {
    l = new(logger)
    l.lb = getdefaultLogger()
    return
}
//Return the default 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)
}
//console
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...)
        }
    }
}
//log
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 type
func (this *logBean) debug(v ...interface{}) {
    this.log("debug", v...)
}
//info type
func (this *logBean) info(v ...interface{}) {
    this.log("info", v...)
}
//warn type
func (this *logBean) warn(v ...interface{}) {
    this.log("warn", v...)
}
//error type
func (this *logBean) error(v ...interface{}) {
    this.log("error", v...)
}

//Check if the file needs to be renamed
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)
        }
    }
}

//--------------------------------------------------------------------------------
//Whether to rename
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
}
//Find the next suffix
func (this *fileBean) nextSuffix() int {
    return int(this._suffix%int(this.maxFileCount) + 1)
}
//New file generation
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
}
//rename
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)
}
//Add size to &this.filesize
func (this *fileBean) addsize(size int64) {
    atomic.AddInt64(&this.filesize, size)
}
//File format format is empty, the way to write files
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))
}
//File format format is not empty, the way to write files
func (this *fileBean) writef(format string, v ...interface{}) {
    this.mu.RLock()
    defer this.mu.RUnlock()
    this.lg.Output(skip+1, fmt.Sprintf(format, v...))
}
//Whether it exceeds the size
func (this *fileBean) isOverSize() bool {
    return this.filesize >= this.maxFileSize
}
//Close file
func (this *fileBean) close() {
    this.logfile.Close()
}


//Create log file directory
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
}
//File size
func fileSize(file string) int64 {
    f, e := os.Stat(file)
    if e != nil {
        fmt.Println(e.Error())
        return 0
    }
    return f.Size()
}
//Whether the path exists
func isExist(path string) bool {
    _, err := os.Stat(path)
    return err == nil || os.IsExist(err)
}
//[]byte -> String (hexadecimal)
//String to represent hexadecimal
func md5str(s string) string {
    m := md5.New()
    m.Write([]byte(s))
    return hex.EncodeToString(m.Sum(nil))
}
Catch exception
func catchError() {
    if err := recover(); err != nil {
        fmt.Println(string(debug.Stack()))
    }
}

File relations:

relations of log Files

Note:

  • PrintLog is the project folder name,
  • log is the log folder,
  • logger is the place where logger.go and logw.go are placed,
  • main.go is the source code of the main program,
  • the development environment is VSCode + Go, and the rest of the files are generated by the environment. You can do it yourself Operation~

Title log output effect

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
created at:08-08-2021
edited at: 08-08-2021: