Go:log功能的实现
目录:
需求分析
- 支持往不同的地方输出
- 支持日志级别Debug,Trace,Info,Warning,Error,Fatal
- 日志开关功能
- 日志信息格式化
- 日志文件切割
- 异步写入日志
根据日志级别循环打印日志输出
package main
import(
"fmt"
"time"
)
// 各个日志级别
func Debug(msg string) {
now := time.Now()
fmt.Printf("[%s][DEBUG] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}
func Trace(msg string) {
now := time.Now()
fmt.Printf("[%s][TRACE] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}
func Info(msg string) {
now := time.Now()
fmt.Printf("[%s][INFO] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}
func Warning(msg string) {
now := time.Now()
fmt.Printf("[%s][WARNING] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}
func Error(msg string) {
now := time.Now()
fmt.Printf("[%s][ERROR] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}
func Fatal(msg string) {
now := time.Now()
fmt.Printf("[%s][FATAL] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}
// 测试数据
func main() {
for {
Debug("这是Debug日志")
Trace("这是Trace日志")
Info("这是Info日志")
Warning("这是Warning日志")
Error("这是Error日志")
Fatal("这是Fatal日志")
time.Sleep(time.Second)
}
}
日志开关
将日志级别转化为数字,用于比较日志级别输出
package main
import(
"fmt"
"time"
"errors"
)
type LogLevel uint16
const (
UNKNOWN LogLevel = iota
DEBUG
TRACE
INFO
WARNING
ERROR
FATAL
)
type Logger struct {
Level LogLevel
}
// 构造函数
func NewLogger(levelStr string) Logger {
level, err := parseLogLevel(levelStr)
if err != nil {
panic(err)
}
return Logger{
Level: level,
}
}
// 日志字符串转化为日志级别
func parseLogLevel(s string) (LogLevel, error) {
switch s {
case "debug":
return DEBUG, nil
case "trace":
return TRACE, nil
case "info":
return INFO, nil
case "warning":
return WARNING, nil
case "error":
return ERROR, nil
case "fatal":
return FATAL, nil
default:
err := errors.New("无效日志级别")
return UNKNOWN, err
}
}
// 开关
func (l Logger) enable(LogLevel LogLevel) bool {
return LogLevel >= l.Level
}
// 各个日志级别
func (l Logger) Debug(msg string) {
if l.enable(DEBUG) {
now := time.Now()
fmt.Printf("[%s][DEBUG] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}
}
func (l Logger) Trace(msg string) {
if l.enable(TRACE) {
now := time.Now()
fmt.Printf("[%s][TRACE] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}
}
func (l Logger) Info(msg string) {
if l.enable(INFO) {
now := time.Now()
fmt.Printf("[%s][INFO] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}
}
func (l Logger) Warning(msg string) {
if l.enable(WARNING) {
now := time.Now()
fmt.Printf("[%s][WARNING] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}
}
func (l Logger) Error(msg string) {
if l.enable(ERROR) {
now := time.Now()
fmt.Printf("[%s][ERROR] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}
}
func (l Logger) Fatal(msg string) {
if l.enable(FATAL) {
now := time.Now()
fmt.Printf("[%s][FATAL] %s\n", now.Format("2006-01-02 15:04:05"), msg)
}
}
// 测试数据
func main() {
log := NewLogger("error")
for {
log.Debug("这是Debug日志")
log.Trace("这是Trace日志")
log.Info("这是Info日志")
log.Warning("这是Warning日志")
log.Error("这是Error日志")
log.Fatal("这是Fatal日志")
time.Sleep(time.Second)
}
}
定位错位位置
使用runtime.Caller实现
package main
import(
"fmt"
"time"
"errors"
"runtime"
"strings"
"path"
)
type LogLevel uint16
const (
UNKNOWN LogLevel = iota
DEBUG
TRACE
INFO
WARNING
ERROR
FATAL
)
type Logger struct {
Level LogLevel
}
// 构造函数
func NewLogger(levelStr string) Logger {
level, err := parseLogLevel(levelStr)
if err != nil {
panic(err)
}
return Logger{
Level: level,
}
}
// 定位错误位置
func getInfo(skip int) (fileName, funcName string, lineNo int) {
pc, file, lineNo, ok := runtime.Caller(skip)
if !ok {
fmt.Printf("runtimme.Caller() failed\n")
return
}
funcName = runtime.FuncForPC(pc).Name()
fileName = path.Base(file)
funcName = strings.Split(funcName, ".")[1]
return
}
// 日志字符串转化为日志级别
func parseLogLevel(s string) (LogLevel, error) {
switch s {
case "debug":
return DEBUG, nil
case "trace":
return TRACE, nil
case "info":
return INFO, nil
case "warning":
return WARNING, nil
case "error":
return ERROR, nil
case "fatal":
return FATAL, nil
default:
err := errors.New("无效日志级别")
return UNKNOWN, err
}
}
// 开关
func (l Logger) enable(LogLevel LogLevel) bool {
return LogLevel >= l.Level
}
// 各个日志级别
func (l Logger) Debug(msg string) {
if l.enable(DEBUG) {
now := time.Now()
fileName, funcName, lineNo := getInfo(1)
fmt.Printf("[%s][DEBUG][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), fileName, funcName, lineNo, msg)
}
}
func (l Logger) Trace(msg string) {
if l.enable(TRACE) {
now := time.Now()
fileName, funcName, lineNo := getInfo(1)
fmt.Printf("[%s][TRACE][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), fileName, funcName, lineNo, msg)
}
}
func (l Logger) Info(msg string) {
if l.enable(INFO) {
now := time.Now()
fileName, funcName, lineNo := getInfo(1)
fmt.Printf("[%s][INFO][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), fileName, funcName, lineNo, msg)
}
}
func (l Logger) Warning(msg string) {
if l.enable(WARNING) {
now := time.Now()
fileName, funcName, lineNo := getInfo(1)
fmt.Printf("[%s][WARNING][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), fileName, funcName, lineNo, msg)
}
}
func (l Logger) Error(msg string) {
if l.enable(ERROR) {
now := time.Now()
fileName, funcName, lineNo := getInfo(1)
fmt.Printf("[%s][ERROR][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), fileName, funcName, lineNo, msg)
}
}
func (l Logger) Fatal(msg string) {
if l.enable(FATAL) {
now := time.Now()
fileName, funcName, lineNo := getInfo(1)
fmt.Printf("[%s][FATAL][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), fileName, funcName, lineNo, msg)
}
}
// 测试数据
func main() {
log := NewLogger("error")
for {
log.Debug("这是Debug日志")
log.Trace("这是Trace日志")
log.Info("这是Info日志")
log.Warning("这是Warning日志")
log.Error("这是Error日志")
log.Fatal("这是Fatal日志")
time.Sleep(time.Second)
}
}
重复日志打印整合
package main
import(
"fmt"
"time"
"errors"
"runtime"
"strings"
"path"
)
type LogLevel uint16
const (
UNKNOWN LogLevel = iota
DEBUG
TRACE
INFO
WARNING
ERROR
FATAL
)
type Logger struct {
Level LogLevel
}
// 构造函数
func NewLogger(levelStr string) Logger {
level, err := parseLogLevel(levelStr)
if err != nil {
panic(err)
}
return Logger{
Level: level,
}
}
// 定位错误位置
func getInfo(skip int) (fileName, funcName string, lineNo int) {
pc, file, lineNo, ok := runtime.Caller(skip)
if !ok {
fmt.Printf("runtimme.Caller() failed\n")
return
}
funcName = runtime.FuncForPC(pc).Name()
fileName = path.Base(file)
funcName = strings.Split(funcName, ".")[1]
return
}
// 日志字符串转化为日志级别
func parseLogLevel(s string) (LogLevel, error) {
switch s {
case "debug":
return DEBUG, nil
case "trace":
return TRACE, nil
case "info":
return INFO, nil
case "warning":
return WARNING, 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 WARNING:
return "WARNING"
case ERROR:
return "ERROR"
case FATAL:
return "FATAL"
default:
return "UNKNOWN"
}
}
// 开关
func (l Logger) enable(LogLevel LogLevel) bool {
return LogLevel >= l.Level
}
// 各个日志级别
func (l Logger) log(lvstring string , msg string) {
lv, err := parseLogLevel(lvstring)
if err != nil {
panic(err)
}
if l.enable(lv) {
now := time.Now()
// skip根据实际情况调整
fileName, funcName, lineNo := getInfo(1)
fmt.Printf("[%s][%s][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), fileName, funcName, lineNo, msg)
}
}
// 测试数据
func main() {
log := NewLogger("error")
for {
log.log("fatal", "这是Fatal日志")
log.log("error", "这是error日志")
log.log("warning", "这是warning日志")
log.log("info", "这是info日志")
log.log("debug", "这是debug日志")
time.Sleep(time.Second)
}
}
自定义日志格式
采用空接口传数据
// 各个日志级别
func (l Logger) log(lvstring string , format string, a ...interface{}) {
msg := fmt.Sprintf(format, a...)
lv, err := parseLogLevel(lvstring)
if err != nil {
panic(err)
}
if l.enable(lv) {
now := time.Now()
// skip根据实际情况调整
fileName, funcName, lineNo := getInfo(1)
fmt.Printf("[%s][%s][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), fileName, funcName, lineNo, msg)
}
}
// 测试数据
func main() {
log := NewLogger("error")
id := 1
st := "test"
for {
log.log("fatal", "这是Fatal日志, id=%d, st=%s", id, st)
log.log("error", "这是error日志")
log.log("warning", "这是warning日志")
log.log("info", "这是info日志")
log.log("debug", "这是debug日志")
time.Sleep(time.Second)
}
}
打印结果
[2020-02-20 18:04:46][FATAL][main.go Logger 107] 这是Fatal日志, id=1, st=test
[2020-02-20 18:04:46][ERROR][main.go Logger 107] 这是error日志
[2020-02-20 18:04:47][FATAL][main.go Logger 107] 这是Fatal日志, id=1, st=test
[2020-02-20 18:04:47][ERROR][main.go Logger 107] 这是error日志
[2020-02-20 18:04:48][FATAL][main.go Logger 107] 这是Fatal日志, id=1, st=test
[2020-02-20 18:04:48][ERROR][main.go Logger 107] 这是error日志
写入文件
package main
import(
"fmt"
"time"
"errors"
"runtime"
"strings"
"path"
"os"
)
type LogLevel uint16
const (
UNKNOWN LogLevel = iota
DEBUG
TRACE
INFO
WARNING
ERROR
FATAL
)
type FileLogger struct {
Level LogLevel
filePath string //日志路径
fileName string //日志名
maxFileSize int64 //日志切分大小
fileObj *os.File
}
// 构造函数
func NewFileLogger(levelStr, fp, fn string, maxSize int64) *FileLogger {
logLevel, err := parseLogLevel(levelStr)
if err != nil {
panic(err)
}
f := &FileLogger{
Level: logLevel,
filePath: fp,
fileName: fn,
maxFileSize: maxSize,
}
err = f.initFile()
if err != nil {
panic(err)
}
return f
}
// 初始化文件
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\n", err)
return err
}
f.fileObj = fileObj
return nil
}
// 定位错误位置
func getInfo(skip int) (fileName, funcName string, lineNo int) {
pc, file, lineNo, ok := runtime.Caller(skip)
if !ok {
fmt.Printf("runtimme.Caller() failed\n")
return
}
funcName = runtime.FuncForPC(pc).Name()
fileName = path.Base(file)
funcName = strings.Split(funcName, ".")[1]
return
}
// 日志字符串转化为日志级别
func parseLogLevel(s string) (LogLevel, error) {
switch s {
case "debug":
return DEBUG, nil
case "trace":
return TRACE, nil
case "info":
return INFO, nil
case "warning":
return WARNING, 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 WARNING:
return "WARNING"
case ERROR:
return "ERROR"
case FATAL:
return "FATAL"
default:
return "UNKNOWN"
}
}
// 开关
func (f *FileLogger) enable(LogLevel LogLevel) bool {
return LogLevel >= f.Level
}
// 切分日志
func (f *FileLogger) splitFile() {
_, err := f.fileObj.Stat()
if err != nil {
fmt.Printf("get file info failed, err:%v\n", err)
}
f.fileObj.Close()
nowStr := time.Now().Format("20060102150405000")
logName := path.Join(f.filePath, f.fileName)
newLogName := fmt.Sprintf("%s-%s", logName, nowStr)
os.Rename(logName, newLogName)
fileObj, err := os.OpenFile(logName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("Open log file failed, err:%v\n", err)
return
}
f.fileObj = fileObj
}
// 判断日志大小
func (f *FileLogger) checkSize() bool {
fileInfo, err := f.fileObj.Stat()
if err != nil {
fmt.Printf("get file info failed, err:%v\n", err)
}
return fileInfo.Size() > f.maxFileSize
}
// 各个日志级别
func (f *FileLogger) log(lvstring string , format string, a ...interface{}) {
lv, _ := parseLogLevel(lvstring)
if f.enable(lv) {
msg := fmt.Sprintf(format, a...)
now := time.Now()
// skip根据实际情况调整
fileName, funcName, lineNo := getInfo(1)
if f.checkSize() {
f.splitFile()
}
fmt.Fprintf(f.fileObj, "[%s][%s][%s %s %d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), fileName, funcName, lineNo, msg)
}
}
// 测试数据
func main() {
log := NewFileLogger("info", "./", "why.test", 10*1024)
id := 1
st := "test"
for {
log.log("fatal", "这是Fatal日志, id=%d, st=%s", id, st)
log.log("error", "这是error日志")
log.log("warning", "这是warning日志")
log.log("info", "这是info日志")
log.log("debug", "这是debug日志")
time.Sleep(time.Second)
}
}
查看一下
$ ll
-rw-r--r-- 1 root root 4202 Feb 20 21:36 main.go
-rw-r--r-- 1 root root 3980 Feb 20 21:33 why.test
-rw-r--r-- 1 root root 10257 Feb 20 21:29 why.test-20200220212908000
-rw-r--r-- 1 root root 10251 Feb 20 21:29 why.test-20200220212941000
-rw-r--r-- 1 root root 10255 Feb 20 21:30 why.test-20200220213014000
-rw-r--r-- 1 root root 10329 Feb 20 21:30 why.test-20200220213047000
-rw-r--r-- 1 root root 10329 Feb 20 21:31 why.test-20200220213120000
-rw-r--r-- 1 root root 10329 Feb 20 21:31 why.test-20200220213153000
-rw-r--r-- 1 root root 10329 Feb 20 21:32 why.test-20200220213226000
-rw-r--r-- 1 root root 10329 Feb 20 21:32 why.test-20200220213259000
$ cat why.test
[2020-02-20 21:33:02][WARNING][main.go (*FileLogger) 158] 这是warning日志
[2020-02-20 21:33:02][INFO][main.go (*FileLogger) 158] 这是info日志
[2020-02-20 21:33:03][FATAL][main.go (*FileLogger) 158] 这是Fatal日志, id=1, st=test
[2020-02-20 21:33:03][ERROR][main.go (*FileLogger) 158] 这是error日志
[2020-02-20 21:33:03][WARNING][main.go (*FileLogger) 158] 这是warning日志
[2020-02-20 21:33:03][INFO][main.go (*FileLogger) 158] 这是info日志
[2020-02-20 21:33:04][FATAL][main.go (*FileLogger) 158] 这是Fatal日志, id=1, st=test
[2020-02-20 21:33:04][ERROR][main.go (*FileLogger) 158] 这是error日志
[2020-02-20 21:33:04][WARNING][main.go (*FileLogger) 158] 这是warning日志
[2020-02-20 21:33:04][INFO][main.go (*FileLogger) 158] 这是info日志
[2020-02-20 21:33:05][FATAL][main.go (*FileLogger) 158] 这是Fatal日志, id=1, st=test
[2020-02-20 21:33:05][ERROR][main.go (*FileLogger) 158] 这是error日志
[2020-02-20 21:33:05][WARNING][main.go (*FileLogger) 158] 这是warning日志
[2020-02-20 21:33:05][INFO][main.go (*FileLogger) 158] 这是info日志
如果是异步写,需要将消息的指针通过channel传递过去