自定义一个日志库。
知识储备
runtime.Caller()
该方法能够获取到打印的位置,文件的信息,行数等。
以下是该方法的使用,不必纠结太多,照着用就行。
唯一注意的是caller() 中值的放入,该值会影响行数的显示,多测试几遍你就大概明白了。
package main
import (
"fmt"
"runtime"
"path/filepath"
)
func f1() {
pc, file, line, ok := runtime.Caller(1) // 当被其他函数调用,则设置为1. 当无其他函数调用则设置为0
if !ok {
fmt.Println("获取信息时出错")
return
}
funcName := runtime.FuncForPC(pc).Name() // 获取调用该函数的函数名字
fmt.Println(funcName) // 打印调用者姓名
fmt.Println(filepath.Base(file)) // 打印文件
fmt.Println(line) // 打印行
}
func main() {
f1()
}
errors.New()
该方法用于在函数中返回一个error 对象时,添加一个新错误。
package main
import (
"errors"
"fmt"
)
func f1() (string, error) {
return "哈哈哈", errors.New("新错误")
}
func main() {
str, err := f1()
fmt.Println(err) // 新错误
fmt.Println(str) // 哈哈哈
}
具体代码
myLogger.go
package mylogger
import (
"errors"
"fmt"
"path/filepath"
"runtime"
"strings"
)
// LogLevel 日志级别 (自类型)
type LogLevel uint16
// Logger 接口
type Logger interface {
Debug(format string, args ...interface{})
Trace(format string, args ...interface{})
Info(format string, args ...interface{})
Warning(format string, args ...interface{})
Error(format string, args ...interface{})
Fatal(format string, args ...interface{})
}
// 定义日志级别
const (
UNKNOWN LogLevel = iota
DEBUG
TRACE
INFO
WARNING
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 "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 "DEBUG"
}
}
func getInfo(skip int) (funcName, fileName string, lineNo int) {
pc, file, line, ok := runtime.Caller(skip)
if !ok {
fmt.Printf("runtime.Caller() failed\n")
return
}
funcName = runtime.FuncForPC(pc).Name()
funcName = strings.Split(funcName, ".")[1]
fileName = filepath.Base(file)
lineNo = line
return funcName, fileName, lineNo
}
file.go
package mylogger
import (
"fmt"
"os"
"path/filepath"
"time"
)
// FileLogger 往文件里面写日志相关代码
type FileLogger struct {
level LogLevel
filePath string // 日志文件保存路径
fileName string // 日志文件名
maxFileSize int64 // 最大的文件大小
fileObj *os.File
errFileObj *os.File
}
// NewFileLogger ...
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 (f *FileLogger) initFile() error {
// 创建记录正确的日志文件
fullFileName := filepath.Join(f.filePath, f.fileName)
fileObj, err := os.OpenFile(fullFileName+".log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("open log file faild,err:%v", err)
return err
}
// 创建错误的日志文件
errFileObj, err := os.OpenFile(fullFileName+"_err.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("open err log file faild,err:%v", err)
return err
}
f.fileObj = fileObj
f.errFileObj = errFileObj
return nil
}
func (f *FileLogger) enable(logLevel LogLevel) bool {
return f.level <= logLevel
}
func (f *FileLogger) log(lv LogLevel, format string, args ...interface{}) {
if f.enable(lv) {
msg := fmt.Sprintf(format, args...) // 合并输出
funcName, fileName, lineNo := getInfo(3) // 三层调用
now := time.Now().Format("2006-01-02 03:04:06")
lvStr := getLogString(lv)
if f.checkSize(f.fileObj) {
newFile, err := f.splitFile(f.fileObj)
if err != nil {
return
}
f.fileObj = newFile
}
fmt.Fprintf(f.fileObj, "[%s] [%s] [%s:%s:%d] %s \n", now, lvStr, fileName, funcName, lineNo, msg)
if lv >= ERROR {
if f.checkSize(f.errFileObj) {
newFile, err := f.splitFile(f.errFileObj)
if err != nil {
return
}
f.errFileObj = newFile
}
// 如果记录日志级别大于或等于ERROR,则再记录一份到LogErr的文件中
fmt.Fprintf(f.errFileObj, "[%s] [%s] [%s:%s:%d] %s \n", now, lvStr, fileName, funcName, lineNo, msg)
}
}
}
// Debug ...
func (f *FileLogger) Debug(format string, args ...interface{}) {
f.log(DEBUG, format, args...)
}
// Trace ...
func (f *FileLogger) Trace(format string, args ...interface{}) {
f.log(TRACE, format, args...)
}
// Info ...
func (f *FileLogger) Info(format string, args ...interface{}) {
f.log(INFO, format, args...)
}
// Warning ...
func (f *FileLogger) Warning(format string, args ...interface{}) {
f.log(WARNING, format, args...)
}
// Error ...
func (f *FileLogger) Error(format string, args ...interface{}) {
f.log(ERROR, format, args...)
}
// Fatal ...
func (f *FileLogger) Fatal(format string, args ...interface{}) {
f.log(FATAL, format, args...)
}
// Close 关闭文件资源
func (f *FileLogger) Close() {
f.fileObj.Close()
f.errFileObj.Close()
}
// 获取文件大小,判断是否要进行切割
func (f *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
}
// 如果当前文件的size大于设定的size,则返回true,否则返回false
return fileInfo.Size() >= f.maxFileSize
}
func (f *FileLogger) splitFile(file *os.File) (*os.File, error) {
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 := filepath.Join(f.filePath, fileInfo.Name())
newlogName := fmt.Sprintf("%s.%s.bak", logName, nowStr)
// 1. 关闭当前文件
file.Close()
// 2. 备份一个 rename
os.Rename(logName, newlogName)
// 3. 打开一个新的日志文件
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", err)
return nil, err
}
// 4. 将打开的文件赋值给 fl.FileObj
return fileObj, nil
}
consloe.go
package mylogger
import (
"fmt"
"time"
)
// 往终端写日志
// ConsoleLogger ...
type ConsoleLogger struct {
level LogLevel
}
// NewConsoleLogger 构造函数 ...
func NewConsoleLogger(levelStr string) ConsoleLogger {
level, err := parseLogLevel(levelStr)
if err != nil {
panic(err)
}
return ConsoleLogger{
level: level,
}
}
func (c ConsoleLogger) enable(logLevel LogLevel) bool {
return c.level <= logLevel
}
func (c ConsoleLogger) log(lv LogLevel, format string, args ...interface{}) {
if c.enable(lv) {
msg := fmt.Sprintf(format, args...) // 合并输出
funcName, fileName, lineNo := getInfo(3) // 三层调用
now := time.Now().Format("2006-01-02 03:04:06")
lvStr := getLogString(lv)
fmt.Printf("[%s] [%s] [%s:%s:%d] %s \n", now, lvStr, fileName, funcName, lineNo, msg)
}
}
// Debug ...
func (c ConsoleLogger) Debug(format string, args ...interface{}) {
c.log(DEBUG, format, args...)
}
// TRACE ...
func (c ConsoleLogger) Trace(format string, args ...interface{}) {
c.log(TRACE, format, args...)
}
// Info ...
func (c ConsoleLogger) Info(format string, args ...interface{}) {
c.log(INFO, format, args...)
}
// Warning ...
func (c ConsoleLogger) Warning(format string, args ...interface{}) {
c.log(WARNING, format, args...)
}
// Error ...
func (c ConsoleLogger) Error(format string, args ...interface{}) {
c.log(ERROR, format, args...)
}
// Fatal ...
func (c ConsoleLogger) Fatal(format string, args ...interface{}) {
c.log(FATAL, format, args...)
}
使用案例
日志库分为往屏幕打印与往文件写入两种。
通过构造函数实例化不同的对象然后进行操作,支持格式化。
支持文件切割,可指定每个日志文件的大小。
package main
import (
mylogger "yunya.com/module"
)
func main() {
fileLog := mylogger.NewFileLogger("debug", "./", "test", 3*1024) // 向文件打印
consoleLog := mylogger.NewConsoleLogger("debug")
for {
fileLog.Debug("Debug%v", "试试")
fileLog.Info("Info")
fileLog.Warning("Warning")
fileLog.Error("Error")
fileLog.Fatal("Fatal")
consoleLog.Debug("Debug")
}
}
异步写入
以下是对写入文件的代码进行优化。会自动开一个goroutine来写入内容。
package mylogger
import (
"fmt"
"os"
"path/filepath"
"time"
)
// FileLogger 往文件里面写日志相关代码
type FileLogger struct {
level LogLevel
filePath string // 日志文件保存路径
fileName string // 日志文件名
maxFileSize int64 // 最大的文件大小
fileObj *os.File
errFileObj *os.File
logChan chan *logMsg
}
type logMsg struct {
Level LogLevel
msg string
funcName string
line int
fileName string
timestamp string // 时间戳
}
// NewFileLogger ...
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,
logChan: make(chan *logMsg, 50000), // 容量大小五万的日志通道
}
err = fl.initFile() // 打开文件,获取文件对象
if err != nil {
panic(err)
}
return fl
}
func (f *FileLogger) initFile() error {
// 创建记录正确的日志文件
fullFileName := filepath.Join(f.filePath, f.fileName)
fileObj, err := os.OpenFile(fullFileName+".log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("open log file faild,err:%v", err)
return err
}
// 创建错误的日志文件
errFileObj, err := os.OpenFile(fullFileName+"_err.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("open err log file faild,err:%v", err)
return err
}
f.fileObj = fileObj
f.errFileObj = errFileObj
// 开启后台goroutine写日志
go f.writeLogBackground()
return nil
}
func (f *FileLogger) enable(logLevel LogLevel) bool {
return f.level <= logLevel
}
func (f *FileLogger) writeLogBackground() {
for {
if f.checkSize(f.fileObj) {
newFile, err := f.splitFile(f.fileObj)
if err != nil {
return
}
f.fileObj = newFile
}
select {
case logTmp := <-f.logChan:
logInfo := fmt.Sprintf("[%s] [%s] [%s:%s:%d] %s \n", logTmp.timestamp, getLogString(logTmp.Level), logTmp.fileName, logTmp.funcName, logTmp.line, logTmp.msg)
fmt.Fprintf(f.fileObj, logInfo)
if logTmp.Level >= ERROR {
if f.checkSize(f.errFileObj) {
newFile, err := f.splitFile(f.errFileObj)
if err != nil {
return
}
f.errFileObj = newFile
}
// 如果记录日志级别大于或等于ERROR,则再记录一份到LogErr的文件中
fmt.Fprintf(f.fileObj, logInfo)
}
default:
// 等五百毫秒
time.Sleep(time.Millisecond * 500)
}
}
}
func (f *FileLogger) log(lv LogLevel, format string, args ...interface{}) {
if f.enable(lv) {
msg := fmt.Sprintf(format, args...) // 合并输出
funcName, fileName, lineNo := getInfo(3) // 三层调用
now := time.Now().Format("2006-01-02 03:04:06")
// 日志发送到通道中
logTmp := &logMsg{
Level: lv,
msg: msg,
funcName: funcName,
fileName: fileName,
timestamp: now,
line: lineNo,
}
select {
case f.logChan <- logTmp:
// 信息,写入通道
default:
// 如果写满了通道就不写了,丢掉写不进去的日志
}
}
}
// Debug ...
func (f *FileLogger) Debug(format string, args ...interface{}) {
f.log(DEBUG, format, args...)
}
// Trace ...
func (f *FileLogger) Trace(format string, args ...interface{}) {
f.log(TRACE, format, args...)
}
// Info ...
func (f *FileLogger) Info(format string, args ...interface{}) {
f.log(INFO, format, args...)
}
// Warning ...
func (f *FileLogger) Warning(format string, args ...interface{}) {
f.log(WARNING, format, args...)
}
// Error ...
func (f *FileLogger) Error(format string, args ...interface{}) {
f.log(ERROR, format, args...)
}
// Fatal ...
func (f *FileLogger) Fatal(format string, args ...interface{}) {
f.log(FATAL, format, args...)
}
// Close 关闭文件资源
func (f *FileLogger) Close() {
f.fileObj.Close()
f.errFileObj.Close()
}
// 获取文件大小,判断是否要进行切割
func (f *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
}
// 如果当前文件的size大于设定的size,则返回true,否则返回false
return fileInfo.Size() >= f.maxFileSize
}
func (f *FileLogger) splitFile(file *os.File) (*os.File, error) {
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 := filepath.Join(f.filePath, fileInfo.Name())
newlogName := fmt.Sprintf("%s.%s.bak", logName, nowStr)
// 1. 关闭当前文件
file.Close()
// 2. 备份一个 rename
os.Rename(logName, newlogName)
// 3. 打开一个新的日志文件
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", err)
return nil, err
}
// 4. 将打开的文件赋值给 fl.FileObj
return fileObj, nil
}
|
请发表评论