go开发实例(日志库简单实现)

1.目录结构

 2.mylogger/concole.go

package mylogger

import (
	"fmt"
	"time"
)

//往终端写日志相关内容

//Logger日志结构体
type ConsoleLogger struct {
	Level LogLevel
}
//NewLog 构造函数
func NewConsoleLogger(LevelStr string) ConsoleLogger {
	level, err := parseLogLevel(LevelStr)
	if err != nil {
		panic(err)
	}
	return ConsoleLogger{
		Level: level,
	}
}
func (c ConsoleLogger) enable(loglevel LogLevel) bool {
	return loglevel >= c.Level
}
func (c ConsoleLogger) log(lv LogLevel, format string, a ...interface{}) {
	if c.enable(lv) {
		msg := fmt.Sprintf(format, a...)
		now := time.Now()
		funcName, fileName, lineNo := getInfo(3)

		fmt.Printf("[%s] [%s] [%s:%s:$d] [%s]\n",now.Format("2006-01-02 15:04:05"), getLogString(lv), fileName, funcName,lineNo, msg)  //
	}
}
func (c ConsoleLogger) Debug(format string, a ...interface{}) {
	c.log(DEBUG, format, a...)
}
func (c ConsoleLogger) Info(format string, a ...interface{}) {
	c.log(INFO, format, a...)
}
func (c ConsoleLogger) Warning(format string, a ...interface{}) {
	c.log(WARNING, format, a...)
}
func (c ConsoleLogger) Error(format string, a ...interface{}) {
	c.log(ERROR, format, a...)
}
func (c ConsoleLogger) Fatal(format string, a ...interface{}) {
	c.log(FATAL, format, a...)
}

3.mylogger/file.go

package mylogger

import (
	"fmt"
	"os"
	"path"
	"time"
)

//aa 往文件里面写日志相关代码
type FileLogger struct {
	Level LogLevel
	filePath string //日志文件保存的路径
	fileName string //日志文件保存的文件名
	fileObj *os.File
	errFileObj *os.File
	maxFileSize int64
	logChan chan *logMsg
}
type logMsg struct {
	level LogLevel
	msg string
	funcName string
	fileName string
	timestamp string
	line int
}
// NewFileLogger 构造函数
func NewFileLogger(levelStr, fp, fn string, maxSize int64) *FileLogger {
	logLevel, err := parseLogLevel(levelStr)
	if err != nil {
		panic(err)
	}
	f1 := &FileLogger {
		Level: logLevel,
		filePath: fp,
		fileName: fn,
		maxFileSize :maxSize,
		logChan: make(chan *logMsg, 50000),
	}
	err = f1.initFile() //按照文件路径和文件名将文件打开
	if err != nil {
		panic(err)
	}
	return f1
}
func (f *FileLogger) initFile() error {
	fullFileName := path.Join(f.filePath, f.fileName)
	fileObj, err := os.OpenFile(fullFileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Printf("open log file failed, err:%v\n", err)
	}
	errFileObj, err := os.OpenFile(fullFileName+ "err",os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Printf("open err log file failed, err:%v\n", err)
		return err
	}
	//日志文件都已打开了
	f.fileObj = fileObj
	f.errFileObj = errFileObj
	//开启一个后台的goroutine去往文件里写日志
	go f.writeLogBackground()
	return nil
}
func (f *FileLogger) enable(logLevel LogLevel) bool {
	return logLevel >= f.Level
}
func (f *FileLogger) checkSize(file *os.File) bool {
	fileInfo, err := file.Stat()
	if err != nil {
		fmt.Printf("get file info failed, err:%v\n",err)
	}
	//如果当前文件大小 大于等于 日志文件的最大值 就应该返回true
	return fileInfo.Size() >= f.maxFileSize
}
func (f *FileLogger) splitFile(file *os.File) (*os.File, error){
	//需要切割日志文件
	nowStr := time.Now().Format("20060102150405000")
	fileInfo, err := file.Stat()
	if err != nil {
		fmt.Printf("get file info failed, err:%v\n", err)
	}
	logName := path.Join(f.filePath,fileInfo.Name())
	newLogName := fmt.Sprintf("%s.bak%s", logName, nowStr)
	//1.关闭当前的日志文件
	file.Close()
	//2.备份一下 rename xx.log ->xx.log.bak2019...
	os.Rename(logName, newLogName)
	fileObj, err := os.OpenFile(logName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Printf("open new log file failed, err:%v\n", err)
		return nil, err
	}
	//4.将打开的新日志文件对象赋值给f.fileObj
	return fileObj, nil
}
func (f *FileLogger) writeLogBackground(){
	for {
		if f.checkSize(f.fileObj) {
			newFile, err := f.splitFile(f.fileObj)
			if err != nil {
				return
			}
			f.fileObj = newFile
		}
		select {
			case logTmp := <- f.logChan:
				//把日志拼出来
				logInfo := fmt.Sprintf("[%s] [%s] [%s:%s:$d] [%s]\n",logTmp.timestamp, getLogString(logTmp.level), logTmp.fileName, logTmp.funcName,logTmp.line, logTmp.msg)
				fmt.Fprintf(f.fileObj,logInfo)
				if logTmp.level >= ERROR {
					if f.checkSize(f.errFileObj) {
						newFile, err := f.splitFile(f.errFileObj) //日志文件
						if err != nil{
							return
						}
						f.errFileObj = newFile
					}
					//如果要记录的日志大于等于ERROR级别,我还要在err日志文件中再记录一遍
					fmt.Fprintf(f.errFileObj,logInfo) //
				}
		default:
			//取不到日志先休息500毫秒
			time.Sleep(time.Millisecond * 500)
		}
	}
}

func (f *FileLogger) log(lv LogLevel, format string, a ...interface{}){
	if f.enable(lv) {
		msg := fmt.Sprintf(format, a...)
		now := time.Now()
		funcName, fileName, lineNo := getInfo(3)
		//先把日志发送到通道中
		logTmp := &logMsg{
			level: lv,
			msg: msg,
			funcName: funcName,
			fileName: fileName,
			timestamp: now.Format("2016-01-02 15:04:05"),
			line: lineNo,
		}
		select {
		case f.logChan <- logTmp:
		default:
		}

	}
}
func (f *FileLogger) Debug(format string, a ...interface{}){
	f.log(DEBUG, format, a...)
}
func (f *FileLogger) Info(format string, a ...interface{}){
	f.log(INFO, format, a...)
}
func (f *FileLogger) Warning(format string, a ...interface{}){
	f.log(WARNING, format, a...)
}
func (f *FileLogger) Error(format string, a ...interface{}){
	f.log(ERROR, format, a...)
}
func (f *FileLogger) Fatal(format string, a ...interface{}){
	f.log(FATAL, format, a...)
}

4.mylogger/mylogger.go

package mylogger

import (
	"errors"
	"fmt"
	"path"
	"runtime"
	"strings"
)

//自定义一个日志库
type LogLevel uint16
//Logger接口
type Logger interface {
	Debug(format string, a ...interface{})
	Info(format string, a ...interface{})
	Warning(format string, a ...interface{})
	Error(format string, a ...interface{})
	Fatal(format string, a ...interface{})
}
const (
	UNKNOWN LogLevel = iota
	DEBUG
	TRACE
	INFO
	WARNING
	ERROR
	FATAL
)
func parseLogLevel(s string) (LogLevel, error){
	s = strings.ToLower(s)
	switch s {
	case "debug":
		return DEBUG, nil
	case "trace":
		return TRACE, nil
	case "info":
		return INFO, nil
	case "warning":
		return WARNING, nil
	case "error":
		return ERROR, nil
	case "fatal":
		return FATAL, nil
	default:
		err := errors.New("无效的日志级别")
		return UNKNOWN, err
	}
}
func getLogString(lv LogLevel) string{
	switch lv {
	case DEBUG:
		return "DEBUG"
	case TRACE:
		return "TRACE"
	case INFO:
		return "INFO"
	case WARNING:
		return "WARNING"
	case ERROR:
		return "ERROR"
	case FATAL:
		return "FATAL"
	}
	return "DEBUG"
}
func getInfo(skip int) (funcName, fileName string, lineNo int) {
	pc, file, lineNo, ok := runtime.Caller(skip)
	if !ok {
		fmt.Printf("runtime.Caller() failed\n")
		return
	}
	funcName = runtime.FuncForPC(pc).Name()
	fileName = path.Base(file)
	funcName = strings.Split(funcName, ".")[1]
	return
}

5.mylogger_test

package main

import (
	"day06/mylogger"
)
var log mylogger.Logger
//测试我们自己写的日志库
func main(){
	log = mylogger.NewConsoleLogger("Info")
	log = mylogger.NewFileLogger("Info", "./","zhoulinwan.log", 10*1024*1024)
	for {
		log.Debug("这是一条Debug日志")
		log.Info("这是一条info日志")
		log.Warning("这是一条warning日志")
		id := 10010
		name := "理想"
		log.Error("这是一条Error日志,id:%d,name:%s", id, name)
		log.Fatal("这是一条Fatal日志")
	}
}


版权声明:本文为huha666原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。