01。介绍Golang语言的标准库提供了一个简单的日志包,它不仅提供了很多功能,还定义了一个包含很多方法的Logger类型。但是它也有缺点,比如不支持区分日志级别,不支持日志文件切割等。02.函数Golang的日志包主要提供了以下几个带输出功能的函数:funcFatal(v...interface{})funcFatalf(formatstring,v...interface{})funcFatalln(v...interface{})funcPanic(v...interface{})funcPanicf(formatstring,v...interface{})funcPanicln(v...interface{})funcPrint(v...interface{})funcPrintf(formatstring,v...interface{})funcPrintln(v...interface{})这些函数的使用方式与fmt包完全相同。通过查看源码我们可以发现,Fatal[ln|f]和Panic[ln|f]其实是调用了Print[ln|f],而Print[ln|f]其实是在调用Output()函数。其中,Fatal[ln|f]是在调用Print[ln|f]之后,调用os.Exit(1)退出程序。其中,Panic[ln|f]是在调用Panic[ln|f]之后调用panic()函数,抛出一个panic。因此,我们有必要阅读一下Output()函数的源码。Output()函数源码:func(l*Logger)Output(calldepthint,sstring)error{now:=time.Now()//getthisearly.varfilestringvarlineintl.mu.Lock()deferl.mu.Unlock()ifl.flag&(Lshortfile|Llongfile)!=0{//Releaselockwhilegettingcallerinfo-it'sexpensive.l.mu.Unlock()varokbool_,file,line,ok=runtime.Caller(calldepth)if!ok{file="???"line=0}l.mu.Lock()}l.buf=l.buf[:0]l.formatHeader(&l.buf,now,file,line)l.buf=append(l.buf,s...)iflen(s)==0||s[len(s)-1]!='\n'{l.buf=append(l.buf,'\n')}_,err:=l.out.Write(l.buf)returnerr}通过阅读Output()函数的源码可以发现,为了保证多个goroutine写日志的安全,使用了一个mutex,在调用runtime之前释放mutex。caller()函数,获取到信息后,加上互斥锁,保证安全。使用formatHeader()函数格式化日志信息,然后保存在buf中,然后将日志信息追加到buf的末尾,然后通过判断检查日志是否为空或者末尾不是\n,如果so,然后把\n追加到buf的末尾,最后输出日志信息。Output()函数的源码也比较简单,其中最值得注意的是runtime.Caller()函数,源码如下:funcCaller(skipint)(pcuintptr,filestring,lineint,okbool){rpc:=make([]uintptr,1)n:=callers(skip+1,rpc[:])ifn<1{return}frame,_:=CallersFrames(rpc).Next()返回frame.PC,frame。文件,框架。线,框架。PC!=0}通过阅读runtime.Caller()函数的源码,我们可以发现它接收了一个int类型的参数skip,表示跳过的栈帧数,log包中的输出函数使用默认的2.值是什么原因?比如在main函数中调用了log.Print,方法调用栈为main->log.Print->*Logger.Output->runtime.Caller,所以此时参数skip的值为2,表示在main函数中调用log.Print的源文件和代码行号;参数值为1,表示log.Print函数中调用*Logger.Output的源文件和代码行号;参数值为0,表示*Logger.Output函数中调用runtime.Caller的源文件和代码行号。至此,我们发现log包的输出函数都是向控制台输出信息,那么如何向文件输出信息呢?函数SetOutPut用于设置输出目标。源码如下:funcSetOutput(wio.Writer){std.mu.Lock()deferstd.mu.Unlock()std.out=w}我们可以使用函数os.OpenFile打开一个文件进行I/O,返回值作为函数SetOutput的参数。另外,读者应该已经发现了一个问题。输出信息以日期和时间开头。如何记录更丰富的信息?例如,源文件和行号。这使用了函数SetFlags,它可以设置输出格式。源码如下:funcSetFlags(flagint){std.SetFlags(flag)}参数flag的值可以是以下任一常量:const(Ldate=1<
