需求分析:
1.支持往不同的地方输出日志(终端还是指定的文件)
2.日志分级别(DEBUG,WANRING,ERROR…)
3.支持开关控制,能设置某级别以上日志才输出
4.完整的日志要有时间,行号,文件名,日志级别,日志信息。
5.日志文件要切割(按文件大小或者日期切割)
func main() {
logger := logfile.NewFileLogger("DEBUG","D:/","guoyilin",1*1024)
name := "郭10"
for {
logger.Debug("DEBUG日志 %s",name)
logger.Warning("Warning日志")
logger.Error("Error日志")
logger.Panic("Panic日志")
time.Sleep(time.Second)
}
}
type LogLevel uint16
//底层的几种日志级别都是int类型
const (
Unknown LogLevel = iota
Debug
Warning
Error
Panic
)
type FileLogger struct {
level LogLevel
filePath string
fileName string
maxFileSize int64
fileObj *os.File //用作写文件时的os.File对象
fileErrObj *os.File
logChan chan *logMsg //用作将来把日志的全部信息传入通道,再由后台goroutine从通道读出来
}
// 放到通道中的日志全部信息, 传入通道时只需传入指针,不需要传整个结构体(数据太大,并且不好控制数据类型)
type logMsg struct {
level LogLevel
logMessage string
fileName string
funcName string
timeStamp string
line int
}
//main方法中,传入的参数是debug,warning等字符串,需要转型到我们自定义的 日志级别类型
func ParseLeverStTOLogLevel(level string) LogLevel {
s:=strings.ToLower(level)
switch s {
case "debug": return Debug
case "warning": return Warning
case "error": return Error
case "panic": return Panic
default:
return Unknown
}
}
//构造方法,生成logger对象
func NewFileLogger (logLevelStr ,filePath ,fileName string ,maxFileSize int64) *FileLogger {
loglevel :=ParseLeverStTOLogLevel(logLevelStr)
f:= &FileLogger{
level: loglevel,
filePath: filePath,
fileName: fileName,
maxFileSize: maxFileSize,
logChan: make(chan *logMsg,50000), //注意channel是引用类型,必须初始化,否则会报空指针
}
err :=f.InitFileObj()
if err!=nil {
panic(err)
}
return f
}
//初始化fileObj 和用于错误级别日志的fileErrObj对象
func (f *FileLogger)InitFileObj () error{
//path.join自动组合带有反斜线的 文件名称
fullName := path.Join(f.filePath,f.fileName)
fmt.Printf("打印path.join方法 fullname值: %s \n",fullName)
fileObj ,err :=os.OpenFile(fullName,os.O_APPEND|os.O_WRONLY|os.O_CREATE ,0644)
if err != nil{
fmt.Println("OpenFile failed ")
return err
}
errObj ,err :=os.OpenFile(fullName+"err.txt",os.O_APPEND|os.O_WRONLY|os.O_CREATE ,0644)
if err != nil{
fmt.Println("ErrFile failed ")
return err
}
//日志文件打开后 进行对象赋值
f.fileObj = fileObj
f.fileErrObj =errObj
//defer fileObj.Close()
//defer errObj.Close()
return nil
}
//获取logger对象调用时,方法名,行号
func getInfo(skip int) (fileName,funcName string ,line int) {
pc,fileName,line ,ok:=runtime.Caller(skip)
if !ok{
fmt.Println("runtime ----error")
return
}
funcName =runtime.FuncForPC(pc).Name()
//取当前路径, 否则默认fileName为绝对路径
//fileName = path.Base(file)
return fileName,funcName,line
}
//后台gorountine 从通道中取值,并且写入文件
func (f *FileLogger) WriteBackGround() {
for {
//先判断是否超过单个日志文件的最大内存,大于的话就分割,重命名一个新文件
if f.checkSize(f.fileObj) {
f.splitFile()
}
logTmp := <- f.logChan
_, error := fmt.Fprintf(f.fileObj,"[%v ][%s] [%s %s 行号:%v ]%s \n", logTmp.level, logTmp.timeStamp, logTmp.fileName, logTmp.funcName, logTmp.line, logTmp.logMessage)
if error != nil {
fmt.Println(error)
}
//设置大于Error级别的日志,输出到专门的文档
if logTmp.level >= Debug {
fmt.Fprintf(f.fileErrObj,"[%v ][%s] [%s %s 行号:%v ]%s \n", logTmp.level, logTmp.timeStamp, logTmp.fileName, logTmp.funcName, logTmp.line, logTmp.logMessage)
}
}
}
func (f *FileLogger)log (lever LogLevel ,msg string,a...interface{}) {
if f.enable(lever) {
//拼接成字符串信息
LogMessage := fmt.Sprintf(msg, a...)
//logRange := ParseLeverIntToString(lever)
now := time.Now()
fileName, funcName, line := getInfo(3)
//把日志发送到通道中
logTmp := &logMsg{
level: lever,
logMessage: LogMessage,
fileName: fileName,
funcName: funcName,
timeStamp: now.Format("2006-01-02 15:04:05"),
line: line,
}
select {
case f.logChan <- logTmp:
for i:=0;i<10 ;i++ {
go f.WriteBackGround()
}
default:
}
}
}
//检查单个日志文件是否超出 默认的设置大小
func (f *FileLogger)checkSize(file *os.File) bool {
fileInfo,err :=file.Stat()
fmt.Println(fileInfo.Size())
if err != nil {
fmt.Printf("get file info failed err: %s" ,err)
return false
}
return fileInfo.Size() >=f.maxFileSize
}
//切割文件,传入一个新的OpenFile参数路径,返回一个新的FileObj,并赋值给 f.fileObj, 用于接下来的写文件
func (f *FileLogger)splitFile () {
now := time.Now()
nowStr := now.Format("2006-01-02-15040500.txt")
OldFileName := path.Join(f.filePath ,f.fileName)
NewFilename := OldFileName+nowStr
fmt.Println(NewFilename)
//3.打开一个新的日志文件
fileNewObj,err := os.OpenFile(NewFilename,os.O_CREATE|os.O_WRONLY|os.O_APPEND ,0644)
if err != nil {
fmt.Printf("log 方法中 180行 出错 err信息 := %s" ,err)
}
f.fileObj = fileNewObj
}
// 控制日志级别输出
func (f *FileLogger)enable(level LogLevel) bool{
return level >= f.level
}
// a... interface{} 方便以后Debug的时候,可以加入任意的信息,比如error信息。
func (f *FileLogger)Debug (msg string ,a... interface{}) {
if f.enable(Debug){
f.log(Debug,msg,a...)
}
}
func (f *FileLogger)Warning (msg string ,a... interface{}) {
if f.enable(Warning){
f.log(Warning,msg,a...)
}
}
func (f *FileLogger)Error (msg string ,a... interface{}) {
if f.enable(Error){
f.log(Error,msg,a...)
}
}
func (f *FileLogger)Panic (msg string ,a... interface{}) {
if f.enable(Panic){
f.log(Panic,msg,a...)
}
}
成果展示:
可以控制Error级别日志以上才输出(比如生产环境上不需要展示DEBUG日志)
有专门的一个日志文件收集全部的错误日志