1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
package mylogger
import (
"fmt"
"os"
"path"
"time"
)
//往文件中写日志
type FileLogger struct {
Level LogLevel
FileName string //日志文件保存的文件名
FilePath string //日志文件保存的路径
MaxFileSize int64 //每个日志文件的最大值
FileObj *os.File //初始化时打开一个文件
ErrFileObj *os.File //记录错误日志的文件
logChan chan *logMsg //管道,用于存放日志信息
}
type logMsg struct {
level LogLevel //日志级别
msg string //日志信息
timestamp string //时间戳
}
//初始化一个Logger结构体
func NewFileLogger(s,fileName,filePath string,maxFileSize int64) *FileLogger{
level,err:=parseLogLevel(s)
if err!=nil{
//如果err不为空,则level为零值=0,0对应着UNKNOW
fmt.Println(err)
os.Exit(0)
}
f1:= &FileLogger{
Level: level,
FileName:fileName,
FilePath: filePath,
MaxFileSize: maxFileSize,
logChan: make(chan *logMsg,50000),
}
err1:=f1.initFile()
if err1!=nil{
//日志文件都无法打开,直接panic中断程序运行
panic(err1)
}
return f1
}
//实现开关级别,通过对象和参数的比较来执行比设置的级别高的日志消息
func (f *FileLogger)enable(loglevel LogLevel)bool{
return loglevel>=f.Level
}
//初始化,打开一个文件向内写日志
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",err)
return err
}
//专门记录错误的日志
errfileObj,err:=os.OpenFile(fullFileName+".err",os.O_APPEND|os.O_CREATE|os.O_WRONLY,0644)
if err!=nil{
fmt.Printf("open log file failed,err:%v",err)
return err
}
f.FileObj=fileObj
f.ErrFileObj=errfileObj
//开启一个后台goroutine往文件内写日志
//不能开启多个,因为同一时间不能有多个程序操作同一个文件
go f.writeLogBackground()
//return nil可以实现调用此方法时加if判断来判断是否有错误
return nil
}
//判断当前文件大小,超过指定值就进行切割,将要写入的文件传进去判断
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)
return false
}
//如果当前文件大小大于等于预设时文件的最大值,就应该返回true,进行切割
return fileInfo.Size()>=f.MaxFileSize
}
func (f *FileLogger)splitFile(file *os.File) (*os.File,error){
//此时则需要进行文件切割,如果返回为false就会直接跳过该判断,继续往原文件写内容
fileInfo,err:=file.Stat()
if err!=nil{
fmt.Printf("open file faile,err:%v\n",err)
return nil,err
}
//然后rename原文件,fileInfo.Name()可以获取文件名,可以同时适配普通文件和错误文件
logName:=path.Join(f.FilePath,fileInfo.Name())//拿到当前日志的路径,join的拼接是用/来拼接的
//在重命名时要先关闭当前的日志文件
nowStr:=time.Now().Format("20060102150405")//获取当前时间
file.Close()
newName:=logName+".bak"+nowStr
os.Rename(logName,newName)//重命名
//打开新的文件并赋值给FileObj
fileObj,err:=os.OpenFile(logName,os.O_APPEND|os.O_CREATE|os.O_WRONLY,0644)
if err!=nil{
fmt.Printf("open file faild,err:%v\n",err)
return f.FileObj,err
}
return fileObj,nil
}
//开携程让日志和业务代码区分开
func (f *FileLogger)writeLogBackground(){
//用for循环让此函数一直运行等待从管道中取数据
for{
//普通日志文件分割
if f.checkSize(f.FileObj){
fileObj,err:=f.splitFile(f.FileObj)
if err!=nil{
fmt.Printf("open file faild,err:%v\n",err)
return
}
f.FileObj=fileObj
}
select {
//如果无法从logChan中取出数据,就一直阻塞在此处
case logtmp:=<-f.logChan:
logInfo:=fmt.Sprintf("[%s] [%s] %s\n",logtmp.timestamp,getLogString(logtmp.level),logtmp.msg)
fmt.Fprintf(f.FileObj,logInfo)
//错误日志文件分割
if logtmp.level>=INFO{
if f.checkSize(f.ErrFileObj){
errfileObj,err:=f.splitFile(f.ErrFileObj)
if err!=nil{
fmt.Printf("open file faild,err:%v",err)
return
}
f.ErrFileObj=errfileObj
}
//如果要记录的日志等级(输入的参数)大于等于INFO级别,则还要在err日志文件中再记录一遍
fmt.Fprintf(f.ErrFileObj,logInfo)
}
}
}
}
//可以通过可变参数(参照fmt.Printf函数)实现格式化输出,实现传变量进字符串
//Sprintf返回一个格式化好的字符串
func (f *FileLogger)logFmt(lv LogLevel,format string,a ...interface{}){
if f.enable(lv){
//设置日志时间格式
var timeInit =time.Now().Format("2006-01-02 15:04:05")
msg:=fmt.Sprintf(format,a...)
//先把日志发到通道中
logtmp:=&logMsg{level: lv,msg:msg,timestamp: timeInit}
//通过select监听管道,避免极端情况下通道阻塞影响业务代码的运行
//写的进去和写不进去都直接跳过,然后继续执行代码
select {
case f.logChan<-logtmp:
default:
//如果写不进去就进行跳过,保证业务代码正常运行,但是此时日志写不进去(极端情况)
}
}
}
func (f *FileLogger)Debug(format string,a ...interface{}){
f.logFmt(DEBUG,format,a...)
}
func (f *FileLogger)Info(format string,a ...interface{}){
f.logFmt(INFO,format,a...)
}
//在用完后记得关闭文件
func (f *FileLogger)Close(){
f.FileObj.Close()
f.ErrFileObj.Close()
}
|