// 直接调用print来输出日志到终端
func main() {
log.Println("这是一条很普通的日志。")
v := "很普通的"
log.Printf("这是一条%s日志。\n", v)
log.Fatalln("这是一条会触发fatal的日志。")
log.Panicln("这是一条会触发panic的日志。")
}
// 获取代码行号
func getinfo(n int) {
/*
runtime.Caller()返回四个参数:
pc: 记录了调用的函数消息,如函数名等
file: 调用的文件名
line: 行数
OK: 返回的bool值
*/
pc, file, line, ok := runtime.Caller(n)
if !ok {
fmt.Printf("runtime.Caller() failed\n")
return
}
fmt.Println(pc) // 用于调用函数消息
funcName := runtime.FuncForPC(pc).Name() // 利用 runtime.FuncForPC()方法,参数pc序号,可以调出函数名
fmt.Println(funcName)
fmt.Println(file) // 打印出执行的文件的绝对路径
fmt.Println(path.Base(file)) // path.Base()方法可以获取传入的绝对路径的最后一个文件名称
fmt.Println(line) // 打印出执行的行号
}
func main() {
getinfo(0)
}
1、在 E:\GoProject\src\gitee.com 项目目录下,新建 mylogger 包目录,存放日志库代码
# E:\GoProject\src\gitee.com\mylogger\console.go
package mylogger
import (
"fmt"
"strings"
"time"
)
// 终端输出的日志内容
// Logger 日志结构体
type ConsoleLogger struct {
Level LogLevel
}
// 构造函数
func NewLog(levelStr string) ConsoleLogger {
level, err := parseLogLevel(levelStr)
if err != nil {
panic(err)
}
return ConsoleLogger{
Level: level,
}
}
// 定义日志等级比较
func (c ConsoleLogger) enable(level LogLevel) bool {
return c.Level <= level
}
// 定义一个日志打印方法
func (c ConsoleLogger) outLog(lv LogLevel, format string, a ...interface{}) {
level := parseLogLevelStr(lv)
if c.enable(lv) {
msg := fmt.Sprintf(format, a...) // 支持格式化操作,a...表示对接收的接口进行展开
level = strings.ToUpper(level)
fileName, funcName, linenu := getinfo(3)
fmt.Printf("[%s] [%s] [FileName:%s lineNum:%d:%s] %s\n", time.Now().Format("2006-01-02 15:04:05"), level, fileName, linenu, funcName, msg)
}
}
// Debug日志
func (c ConsoleLogger) Debug(msg string, a ...interface{}) {
c.outLog(DEBUG, msg, a...)
}
// Info日志
func (c ConsoleLogger) Info(msg string, a ...interface{}) {
c.outLog(INFO, msg, a...)
}
// Warning日志
func (c ConsoleLogger) Warning(msg string, a ...interface{}) {
c.outLog(WARNING, msg, a...)
}
// Error日志
func (c ConsoleLogger) Error(msg string, a ...interface{}) {
c.outLog(ERROR, msg, a...)
}
// Fatal日志
func (c ConsoleLogger) Fatal(msg string, a ...interface{}) {
c.outLog(FATAL, msg, a...)
}
# E:\GoProject\src\gitee.com\mylogger\logfile.go
package mylogger
// 向文件中写日志的相关操作
import (
"fmt"
"os"
"path"
"strings"
"time"
)
// Logger 日志结构体
type FileLogger struct {
Level LogLevel
logPath string // 日志文件目录
fileName string // 日志文件名称
maxFileSize int64 // 日志文件大小
fileObj *os.File
errFileObj *os.File
}
// 构造函数
func NewFileLog(levelStr, fp, fn string, maxSize int64) *FileLogger {
level, err := parseLogLevel(levelStr)
if err != nil {
panic(err)
}
f1 := &FileLogger{
Level: level,
logPath: fp,
fileName: fn,
maxFileSize: maxSize,
}
err = f1.initFile()
if err != nil {
panic(err)
}
return f1
}
// 定义文件初始化方法,打开文件
func (f *FileLogger) initFile() error {
// 打开文件
fullPath := path.Join(f.logPath, f.fileName)
fileObj, err := os.OpenFile(fullPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("open logFile failed,Err:%v", err)
return err
}
// 打开错误日志文件
errFileObj, err := os.OpenFile("error-"+fullPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("open errorLogFile failed,Err:%v", err)
return err
}
// 将文件句柄赋值给结构体中的元素
f.fileObj = fileObj
f.errFileObj = errFileObj
return nil
}
// 关闭文件方法
func (f *FileLogger) closeFile() {
f.fileObj.Close()
f.errFileObj.Close()
}
// 定义日志等级比较
func (f *FileLogger) enable(level LogLevel) bool {
return f.Level <= level
}
// 定义检查日志大小进行切割的方法
func (f *FileLogger) checkLogSize(file *os.File) bool { // 利用os.Open()方法打开的文件句柄类型都是 *os.File 这种指针类型
fileInfo, err := file.Stat()
if err != nil {
fmt.Printf("get file info failed,err:%v\n", err)
return false
// panic(err)
}
if fileInfo.Size() > f.maxFileSize {
return true
}
return false
}
func (f *FileLogger) cutFile(file *os.File) (*os.File, error) {
// 获取当前文件名
fileInfo, err := file.Stat()
if err != nil {
fmt.Printf("get file info failed,err:%v\n", err)
return nil, err
}
append := "_" + time.Now().Format("2006-01-02-15-04-05") + ".log" // 时间戳后缀
fileName := fileInfo.Name()
newFileName := fileName + append
oldPath := path.Join(f.logPath, fileName) // 日志文件的全路径
newPath := path.Join(f.logPath, newFileName)
// 关闭当前文件句柄
file.Close()
// 进行更名备份
os.Rename(oldPath, newPath)
// 再打开一个文件
fileObj, err := os.OpenFile(oldPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("open logFile failed,Err:%v\n", err)
return nil, err
}
return fileObj, nil
}
// 定义一个日志输出的方法
func (f *FileLogger) outLog(lv LogLevel, format string, a ...interface{}) {
level := parseLogLevelStr(lv)
if f.enable(lv) {
msg := fmt.Sprintf(format, a...) // 支持格式化操作,a...表示对接收的接口进行展开
level = strings.ToUpper(level)
fileName, funcName, linenu := getinfo(3)
// 判断日志是否需要切割
if f.checkLogSize(f.fileObj) {
fileobj, err := f.cutFile(f.fileObj)
if err != nil {
fmt.Printf("log cut failed,err:%v\n", err)
}
f.fileObj = fileobj
}
// 将日志记录在日志文件中
fmt.Fprintf(f.fileObj, "[%s] [%s] [FileName:%s lineNum:%d:%s] %s\n", time.Now().Format("2006-01-02 15:04:05"), level, fileName, linenu, funcName, msg)
if lv >= ERROR {
// 判断错误日志是否需要切割
if f.checkLogSize(f.errFileObj) {
fileobj, err := f.cutFile(f.errFileObj)
if err != nil {
fmt.Printf("log cut failed,err:%v\n", err)
}
f.errFileObj = fileobj
}
// 将日志等级大于ERROR的在错误日志中再记录一遍
fmt.Fprintf(f.errFileObj, "[%s] [%s] [FileName:%s lineNum:%d:%s] %s\n", time.Now().Format("2006-01-02 15:04:05"), level, fileName, linenu, funcName, msg)
}
}
}
// Debug日志
func (f *FileLogger) Debug(msg string, a ...interface{}) {
f.outLog(DEBUG, msg, a...)
}
// Info日志
func (f *FileLogger) Info(msg string, a ...interface{}) {
f.outLog(INFO, msg, a...)
}
// Warning日志
func (f *FileLogger) Warning(msg string, a ...interface{}) {
f.outLog(WARNING, msg, a...)
}
// Error日志
func (f *FileLogger) Error(msg string, a ...interface{}) {
f.outLog(ERROR, msg, a...)
}
// Fatal日志
func (f *FileLogger) Fatal(msg string, a ...interface{}) {
f.outLog(FATAL, msg, a...)
}
# E:\GoProject\src\gitee.com\mylogger\mylogger.go
package mylogger
import (
"errors"
"fmt"
"path"
"runtime"
"strings"
)
// 设置日志级别
type LogLevel uint16
const (
UNKNOWN LogLevel = iota
DEBUG
TRACE
INFO
WARNING
ERROR
FATAL
)
// 解析日志等级,将字符串解析成int
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 parseLogLevelStr(lv LogLevel) (s string) {
s = strings.ToLower(s) // 将字符串全部转为小写
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(n int) (fileName, funcName string, line int) {
/*
runtime.Caller()返回四个参数:
pc: 记录了调用的函数消息,如函数名等
file: 调用的文件名
line: 行数
OK: 返回的bool值
*/
pc, file, line, ok := runtime.Caller(n)
if !ok {
fmt.Printf("runtime.Caller() failed\n")
return
}
fileName = path.Base(file)
funcName = runtime.FuncForPC(pc).Name()
return fileName, funcName, line
}
2、调用日志库代码测试
# E:\GoProject\src\gitee.com\LTP\logging
package main
import (
"time"
mylogger "gitee.com/mylogger"
)
func main() {
id := 10
name := "小明"
flogger := mylogger.NewFileLog("info", "./", "app.log", 1024)
for {
flogger.Warning("这是个Warning日志")
flogger.Error("这是个Error日志,id:%d name:%s", id, name)
time.Sleep(2 * time.Second)
}
}