日志库
自定义一个日志库。
知识储备
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,ok := runtime.Caller(skip)
if !ok {
fmt.Printf("runtime.Caller() failedn")
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",0644)
if err != nil {
fmt.Printf("open err log file faild,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,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,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,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,args...)
}
// Info ...
func (f *FileLogger) Info(format string,args ...interface{}) {
f.log(INFO,args...)
}
// Warning ...
func (f *FileLogger) Warning(format string,args ...interface{}) {
f.log(WARNING,args...)
}
// Error ...
func (f *FileLogger) Error(format string,args ...interface{}) {
f.log(ERROR,args...)
}
// Fatal ...
func (f *FileLogger) Fatal(format string,args ...interface{}) {
f.log(FATAL,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%vn",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:%vn",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,0644)
if err != nil {
fmt.Printf("open log file failed,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,args ...interface{}) {
if c.enable(lv) {
msg := fmt.Sprintf(format,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",msg)
}
}
// Debug ...
func (c ConsoleLogger) Debug(format string,args ...interface{}) {
c.log(DEBUG,args...)
}
// TRACE ...
func (c ConsoleLogger) Trace(format string,args ...interface{}) {
c.log(TRACE,args...)
}
// Info ...
func (c ConsoleLogger) Info(format string,args ...interface{}) {
c.log(INFO,args...)
}
// Warning ...
func (c ConsoleLogger) Warning(format string,args ...interface{}) {
c.log(WARNING,args...)
}
// Error ...
func (c ConsoleLogger) Error(format string,args ...interface{}) {
c.log(ERROR,args...)
}
// Fatal ...
func (c ConsoleLogger) Fatal(format string,args ...interface{}) {
c.log(FATAL,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"
ospath/filepathtime"
)
// FileLogger 往文件里面写日志相关代码
type FileLogger struct {
level LogLevel
filePath string 日志文件保存路径
fileName 日志文件名
maxFileSize int64 最大的文件大小
fileObj *os.File
errFileObj *os.File
logChan chan *logMsg
}
type logMsg {
Level LogLevel
msg string
funcName
line int
fileName
timestamp 时间戳
}
NewFileLogger ...
func NewFileLogger(levelStr,fn string,maxSize int64) *FileLogger {
logLevel,err := parseLogLevel(levelStr)
if err != nil {
panic(err)
}
fl := &FileLogger{
level: logLevel,logChan: make(chan *logMsg,50000), 容量大小五万的日志通道
}
err = fl.initFile() 打开文件,获取文件对象
nil {
panic(err)
}
return fl
}
func (f *FileLogger) initFile() error {
创建记录正确的日志文件
fullFileName := filepath.Join(f.filePath,f.fileName)
fileObj,err := os.OpenFile(fullFileName+.log",1)">0644)
nil {
fmt.Printf(open log file faild,err:%v,err)
err
}
创建错误的日志文件
errFileObj,err := os.OpenFile(fullFileName+_err.logopen err log file faild,1)"> err
}
f.fileObj = fileObj
f.errFileObj = errFileObj
开启后台goroutine写日志
go f.writeLogBackground()
nil
}
func (f *FileLogger) enable(logLevel LogLevel) bool {
return f.level <= logLevel
}
func (f *FileLogger) writeLogBackground() {
for {
if f.checkSize(f.fileObj) {
newFile,1)"> f.splitFile(f.fileObj)
nil {
}
f.fileObj = newFile
}
selectcase logTmp := <-f.logChan:
logInfo := fmt.Sprintf([%s] [%s] [%s:%s:%d] %s nif logTmp.Level >= ERROR {
f.checkSize(f.errFileObj) {
newFile,1)"> f.splitFile(f.errFileObj)
nil {
}
f.errFileObj = newFile
}
如果记录日志级别大于或等于ERROR,则再记录一份到LogErr的文件中
fmt.Fprintf(f.fileObj,logInfo)
}
default:
等五百毫秒
time.Sleep(time.Millisecond * 500)
}
}
}
func (f *FileLogger) log(lv LogLevel,format interface{}) {
f.enable(lv) {
msg := fmt.Sprintf(format,args...) 合并输出
funcName,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,}
case f.logChan <- logTmp:
信息,写入通道
如果写满了通道就不写了,丢掉写不进去的日志
}
}
}
Debug ...
func (f *FileLogger) Debug(format {}) {
f.log(DEBUG,args...)
}
Trace ...
func (f *FileLogger) Trace(format {}) {
f.log(TRACE,1)"> Info ...
func (f *FileLogger) Info(format {}) {
f.log(INFO,1)"> Warning ...
func (f *FileLogger) Warning(format {}) {
f.log(WARNING,1)"> Error ...
func (f *FileLogger) Error(format {}) {
f.log(ERROR,1)"> Fatal ...
func (f *FileLogger) Fatal(format {}) {
f.log(FATAL,1)"> Close 关闭文件资源
func (f *FileLogger) Close() {
f.fileObj.Close()
f.errFileObj.Close()
}
获取文件大小,判断是否要进行切割
func (f *FileLogger) checkSize(file *os.File) {
fileInfo,1)"> file.Stat()
get file info failed,err%vnreturn 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:%vn nil,err
}
logName :=%s.%s.bak 1. 关闭当前文件
file.Close()
2. 备份一个 rename
os.Rename(logName,newlogName)
3. 打开一个新的日志文件
fileObj,err := os.OpenFile(logName,1)">open log file failed,err
}
4. 将打开的文件赋值给 fl.FileObj
fileObj,nil
}
?
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|