log包定义了Logger类型,该类型提供了一些格式化输出的方法。本包也提供了一个预定义的“标准”logger,可以通过调用函数Print系列
(Print|Printf|Println)、Fatal系列
(Fatal|Fatalf|Fatalln)、和Panic系列
(Panic|Panicf|Panicln)来使用,比自行创建一个logger对象更容易使用。
例如,我们可以像下面的代码一样直接通过log
包来调用上面提到的方法,默认它们会将日志信息打印到终端界面:
package main
import (
"log"
)
func main() {
log.Println("这是一条很普通的日志。")
v := "很普通的"
log.Printf("这是一条%s日志。\n", v)
log.Fatalln("这是一条会触发fatal的日志。")
log.Panicln("这是一条会触发panic的日志。")
}
编译并执行上面的代码会得到如下输出:
2022/02/14 21:18:42 这是一条很普通的日志。
2022/02/14 21:18:42 这是一条很普通的日志。
2022/02/14 21:18:42 这是一条会触发fatal的日志。
默认情况下的logger只会提供日志的时间信息,但是很多情况下我们希望得到更多信息,比如记录该日志的文件名和行号等。log
标准库中为我们提供了定制这些设置的方法。
log
标准库中的Flags
函数会返回标准logger的输出配置,而SetFlags
函数用来设置标准logger的输出配置。
func Flags() int
func SetFlags(flag int)
log
标准库提供了如下的flag选项,它们是一系列定义好的常量。
const (
// 控制输出日志信息的细节,不能控制输出的顺序和格式。
// 输出的日志在每一项后会有一个冒号分隔:例如2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message
Ldate = 1 << iota // 日期:2009/01/23
Ltime // 时间:01:23:23
Lmicroseconds // 微秒级别的时间:01:23:23.123123(用于增强Ltime位)
Llongfile // 文件全路径名+行号: /a/b/c/d.go:23
Lshortfile // 文件名+行号:d.go:23(会覆盖掉Llongfile)
LUTC // 使用UTC时间
LstdFlags = Ldate | Ltime // 标准logger的初始值
)
下面我们在记录日志之前先设置一下标准logger的输出选项如下:
func main() {
log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
log.Println("这是一条很普通的日志。")
}
编译执行后得到的输出结果如下:
2022/02/14 21:26:42.546952 D:/Golang/src/go_code/project02/log/test/main.go:9: 这是一条很普通的日志。
log
标准库中还提供了关于日志信息前缀的两个方法:
func Prefix() string
func SetPrefix(prefix string)
其中Prefix
函数用来查看标准logger的输出前缀,SetPrefix
函数用来设置输出前缀。
func main() {
log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
log.Println("这是一条很普通的日志。")
log.SetPrefix("[小王子]")
log.Println("这是一条很普通的日志。")
}
上面的代码输出如下:
[小王子]2022/02/14 21:29:01.392368 D:/Golang/src/go_code/project02/log/test/main.go:11: 这是一条很普通的日志。
这样我们就能够在代码中为我们的日志信息添加指定的前缀,方便之后对日志信息进行检索和处理。
func SetOutput(w io.Writer)
SetOutput
函数用来设置标准logger的输出目的地,默认是标准错误输出。
例如,下面的代码会把日志输出到同目录下的xx.log
文件中。
func main() {
logFile, err := os.OpenFile("./xx.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Println("open log file failed, err:", err)
return
}
log.SetOutput(logFile)
log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
log.Println("这是一条很普通的日志。")
log.SetPrefix("[小王子]")
log.Println("这是一条很普通的日志。")
}
如果你要使用标准的logger,我们通常会把上面的配置操作写到init
函数中。
func init() {
logFile, err := os.OpenFile("./xx.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Println("open log file failed, err:", err)
return
}
log.SetOutput(logFile)
log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
}
log
标准库中还提供了一个创建新logger对象的构造函数–New
,支持我们创建自己的logger示例。New
函数的签名如下:
func New(out io.Writer, prefix string, flag int) *Logger
New创建一个Logger对象。其中,参数out设置日志信息写入的目的地。参数prefix会添加到生成的每一条日志前面。参数flag定义日志的属性(时间、文件等等)。
举个例子:
func main() {
logger := log.New(os.Stdout, "<New>", log.Lshortfile|log.Ldate|log.Ltime)
logger.Println("这是自定义的logger记录的日志。")
}
将上面的代码编译执行之后,得到结果如下:
<New>2022/02/14 21:34:36 main.go:11: 这是自定义的logger记录的日志。
公共函数:
package mylogger
import (
"errors"
"fmt"
"path"
"runtime"
"strings"
)
type LogLevel uint16
//接口
type Logger interface {
Debug(format string, a ...interface{})
Info(format string, a ...interface{})
Trace(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
WARING
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 "waring":
return WARING, 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 WARING:
return "WARING"
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.Println("runtime.Caller() failed")
return
}
funcName = runtime.FuncForPC(pc).Name()
fileName = path.Base(file)
funcName = strings.Split(funcName, ".")[1]
return
}
Console:
package mylogger
import (
"fmt"
"time"
)
//往终端上写
//日志结构体
type ConsoleLogger struct {
Level LogLevel
}
//构造函数
func NewConsoleLogLog(levelStr string) ConsoleLogger {
level, err := parseLogLevel(levelStr)
if err != nil {
panic(err)
}
return ConsoleLogger{
level,
}
}
func (l ConsoleLogger) enable(loglevel LogLevel) bool {
return l.Level <= loglevel
}
func (l ConsoleLogger) log(lv LogLevel, format string, a ...interface{}) {
if l.enable(DEBUG) {
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 (l ConsoleLogger) Debug(format string, a ...interface{}) {
l.log(DEBUG, format, a...)
}
func (l ConsoleLogger) Trace(format string, a ...interface{}) {
l.log(TRACE, format, a...)
}
func (l ConsoleLogger) Info(format string, a ...interface{}) {
l.log(INFO, format, a...)
}
func (l ConsoleLogger) Warning(format string, a ...interface{}) {
l.log(WARING, format, a...)
}
func (l ConsoleLogger) Error(format string, a ...interface{}) {
l.log(ERROR, format, a...)
}
func (l ConsoleLogger) Fatal(format string, a ...interface{}) {
l.log(FATAL, format, a...)
}
File
package mylogger
import (
"fmt"
"os"
"path"
"time"
)
//往文件里面写日志相关代码
type FileLogger struct {
Level LogLevel
filePath string //日志文件保存的路径
fileName string //日志文件保存的文件名
fileObj *os.File
errFileObj *os.File
maxFileSize int64
}
// 构造函数
func NewFileLogger(levelStr, fp, fn string, maxSize int64) *FileLogger {
logLevel, err := parseLogLevel(levelStr)
if err != nil {
panic(err)
}
fl := &FileLogger{
Level: logLevel,
filePath: fp,
fileName: fn,
maxFileSize: maxSize,
}
err = fl.initFile()
if err != nil {
panic(err)
}
return fl
}
func (l *FileLogger) initFile() error {
fullFileName := path.Join(l.filePath, l.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)
return 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
}
l.fileObj = fileObj
l.errFileObj = errFileObj
return nil
}
func (l *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
}
return fileInfo.Size() >= l.maxFileSize
}
func (l *FileLogger) splitFile(file *os.File) (*os.File, error) {
//需要切割日志文件
//2.备份一下 rename
nowStr := time.Now().Format("20060102150405000")
fileInfo, err := file.Stat()
if err != nil {
fmt.Printf("get file info failed,err:%v\n", err)
return nil, err
}
logName := path.Join(l.filePath, fileInfo.Name()) //拿到当前的日志文件完整路径
newLogName := fmt.Sprintf("%s.bak%s", logName, nowStr) //拼接一个日志文件备份的名字
//1.关闭当前的日志文件
file.Close()
os.Rename(logName, newLogName)
//3.打开一个新的日志文件
fileObj, err := os.OpenFile(logName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("open new log fail err=", err)
return nil, err
}
//4.将打开的日志文件对象赋值给 fileObj
return fileObj, err
}
func (l *FileLogger) log(lv LogLevel, format string, a ...interface{}) {
if l.enable(lv) {
msg := fmt.Sprintf(format, a...)
now := time.Now()
funcName, fileName, lineNo := getInfo(3)
if l.checkSize(l.fileObj) {
newFile, err := l.splitFile(l.fileObj)
if err != nil {
return
}
l.fileObj = newFile
}
fmt.Fprintf(l.fileObj, "[%s] [%s] [%s : %s : %d] %s \n", now.Format("2006-01-02 15:04:05 "), getLogString(lv), fileName, funcName, lineNo, msg)
if lv >= ERROR {
if l.checkSize(l.errFileObj) {
newFile, err := l.splitFile(l.errFileObj)
if err != nil {
return
}
l.errFileObj = newFile
}
//如果要记录的日志大于等于ERROR级别,我还要在err目志文件中再记录一遍
fmt.Fprintf(l.errFileObj, "[%s] [%s] [%s : %s : %d] %s \n", now.Format("2006-01-02 15:04:05 "), getLogString(lv), fileName, funcName, lineNo, msg)
}
}
}
func (l *FileLogger) enable(loglevel LogLevel) bool {
return l.Level <= loglevel
}
func (l *FileLogger) Debug(format string, a ...interface{}) {
if l.enable(DEBUG) {
l.log(DEBUG, format, a...)
}
}
func (l *FileLogger) Trace(format string, a ...interface{}) {
if l.enable(TRACE) {
l.log(TRACE, format, a...)
}
}
func (l *FileLogger) Info(format string, a ...interface{}) {
if l.enable(INFO) {
l.log(INFO, format, a...)
}
}
func (l *FileLogger) Warning(format string, a ...interface{}) {
if l.enable(WARING) {
l.log(WARING, format, a...)
}
}
func (l *FileLogger) Error(format string, a ...interface{}) {
if l.enable(ERROR) {
l.log(ERROR, format, a...)
}
}
func (l *FileLogger) Fatal(format string, a ...interface{}) {
if l.enable(FATAL) {
l.log(FATAL, format, a...)
}
}
func (l *FileLogger) Close() {
l.fileObj.Close()
l.errFileObj.Close()
}
main:
var log mylogger.Logger
func main() {
log = mylogger.NewConsoleLogLog("debug")
log = mylogger.NewFileLogger("info", "./", "1.log", 10*1024)
for {
id := 100
name := "n"
log.Debug("这是一条 Debug 日志")
log.Info("这是一条 Info 日志")
log.Warning("这是一条 Warning 日志")
log.Error("这是一条 Error 日志 id:%d,name:%s", id, name)
}
}