介绍
golang的log包已经提供了比较完善的功能,我们只是做了一些简单的封装
主要就是构造了
Writer
,这个writer
每次调用Write()
的时候,执行了两步
第一步就是在终端输出,其实就是写到终端里
第二部就是写入到我们的日志文件。写入到日志文件的时候,有个大小判断,超过预定的大小后,就会进行分割和压缩
golang原生日志系统
log包简单实用
func main() {
log.Println("this is my first log")
}
进入到log包中,看Println()
函数
// Println calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Println.
func Println(v ...interface{}) {
std.Output(2, fmt.Sprintln(v...))
}
再网上追溯
Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
var std = New(os.Stderr, "", LstdFlags)
// New creates a new Logger. The out variable sets the
// destination to which log data will be written.
// The prefix appears at the beginning of each generated log line.
// The flag argument defines the logging properties.
func New(out io.Writer, prefix string, flag int) *Logger {
return &Logger{out: out, prefix: prefix, flag: flag}
}
这里的Logger
就是一切的重点了
// A Logger represents an active logging object that generates lines of
// output to an io.Writer. Each logging operation makes a single call to
// the Writer's Write method. A Logger can be used simultaneously from
// multiple goroutines; it guarantees to serialize access to the Writer.
type Logger struct {
mu sync.Mutex // ensures atomic writes; protects the following fields
prefix string // prefix to write at beginning of each line
flag int // properties
out io.Writer // destination for output
buf []byte // for accumulating text to write
}
我们再看Output
函数
func (l *Logger) Output(calldepth int, s string) error {
...
...
_, err := l.out.Write(l.buf)
return err
}
其实就是把日志写入到一个文件中。只是终端有默认的文件而已。
构建我们自己的Logger
上面的例子,当我们没有创建Logger
时,系统会使用默认的Logger
,也就是Stdout
.
接下来,我们构建一个自己的Logger
,让日志输出到我们自己指定的文件中
func main() {
//logFile,_ := os.Open("./test.log") /
logFile, _ := os.OpenFile("test.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
logger := log.New(logFile, "TEST", log.Lshortfile|log.Ldate|log.Ltime)
for i := 0; i < 10; i++ {
logger.Printf("this is my %v test log",i)
}
}
注意两点
-
open(filename)
默认是以只读方式打开,那我们就不能写入了
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}
-
os.O_APPEND
以追加模式
test.log
TEST2019/01/10 14:25:35 test01.go:13: this is my 0 test log
TEST2019/01/10 14:25:35 test01.go:13: this is my 1 test log
TEST2019/01/10 14:25:35 test01.go:13: this is my 2 test log
TEST2019/01/10 14:25:35 test01.go:13: this is my 3 test log
TEST2019/01/10 14:25:35 test01.go:13: this is my 4 test log
...
开始造轮子
第一步就是在终端输出,其实就是写到终端里
第二部就是写入到我们的日志文件。写入到日志文件的时候,有个大小判断,超过预定的大小后,就会进行分割和压缩
接下来展示一下目录结构
mlog 定义了我们日志系统,还有日志子系统
log.go 日志系统接口实现和子系统的创建
interface.go 日志系统接口
rotator
rotator.go 日志写入文件、日志分割、日志压缩
log.go 项目的日志配置文件,创建日志系统及子系统
config.go 项目的配置文件,加载日志等级,用配置的日志文件初始化rotator
main.go 项目入口文件
源码
https://github.com/naichadouban/logProject
分析
首先在log.go
文件中,初始化后台日志系统,还有日志子系统。
var (
backendLog = mlog.NewBackend(logWriter{}) //往终端中写入日志
logRotator *rotator.Rotator // 往文件中写入日志
Mainlog = backendLog.Logger("HCD") //日志子系统
TESTlog = backendLog.Logger("TEST") // 日志子系统
)
然后我们会在config.go
会初始化 logRatator
,设置日志等级
initLogRotator("./test.log") // 这里已经设置了日志输出文件
setLogLevels(DebugLevel)
当我们调用子日志系统打印日志
Mainlog.Infof("this si my log %v",i)
判断日志级别后,调用Backend打印日志,BackendLog统一管理各个子日志系统的日志
func (l *slog) Infof(format string, args ...interface{}) {
lvl := l.Level()
if lvl <= LevelInfo {
l.b.printf("INF", l.tag, format, args...)
}
}
然后我们再看Backend.printf()函数
b.mu.Lock()
b.w.Write(*bytebuf)
b.mu.Unlock()
b.w.Write(*bytebuf)
,这个w.Write()
是我们实现io.Writer接口的Write函数。
他内部有两个Write()
type logWriter struct{}
func (logWriter) Write(p []byte)(n int,err error){
os.Stdout.Write(p) //标准输出,就是我们看到的前台显示
logRotator.Write(p) // rotator的输出,就是文件记录中的操作
return len(p),nil
}
backendLog = mlog.NewBackend(logWriter{})
我们再分析logWriter的Write方法
os.Stdout.Write(p)
这个是系统的标准数据,暂时不讨论
logRotator.Write(p)
func (r *Rotator) Write(p []byte) (n int, err error) {
// 写入文件
n, _ = r.out.Write(p)
r.size += int64(n)
// 日志拆分,压缩
if r.size >= r.threshold && len(p) > 0 && p[len(p)-1] == '\n' {
err := r.rotate()
if err != nil {
return 0, err
}
r.size = 0
}
return n, nil
}
具体的日志压缩,可以看源码