日志
使用Logger
Print系列Fatal系列Panic系列
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
logFlagsSetFlags
func Flags() int
func SetFlags(flag int)
flag选项
log
下面我们在记录日志之前先设置一下标准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)
PrefixSetPrefix
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
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("这是一条很普通的日志。")
}
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)
}
创建logger
logNewNew
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记录的日志。
日志案例
需求分析
- 支持往不同的地方输出日志
- 日志分级别
- Debug
- Trace
- Info
- Warning
- Error
- Fatal
- 日志要支持开关控制,比如说开发的时候什么级别都能输出,但是上线之后只有INFO级别往下的才能输出
- 日志要有时间、行号、文件名、日志级别、日志信息
- 日志文件要切割
- 按文件大小切割
- 每次记录日志之前都判断—下当前写的这个文件的文件大小
- 按日期切割
- 在日志结构体中设置一个字段记录上一次切割的小时数
- 在写日志之前检查一下当前时间的小时数和之前保存的是否一致,不一致就要切割
- 按文件大小切割
公共函数:
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)
}
}