Go语言基础练习五

自定义日志库实现

需求分析

  • 1.支持往不同的地方输出日志
  • 2.日志分级别
    • (1) Debug
    • (2) Trace
    • (3) Info
    • (4) Warning
    • (5) Error
    • (6) Fatal
  • 3.日志要支持开关控制,比如说开发的时候什么级别的都能输出,但是上线之后只有Info级别往下的才能输出
  • 4.完整的日志记录要包含有时间、行号、文件名、日志级别、日志信息
  • 5.日志文件要切割
    • (1) 按文件大小切割
      每次记录日志之前判断一下当前写的这个文件的文件大小
    • (2) 按日期切割



补充知识点runtime.Caller()

这个函数主要是为了查看函数调用栈的问题

pc,file,line, ok := runtime.Caller(n)

返回4个值:

  • n是要提升的堆栈帧数,0-当前函数,1-上一层函数: 就是说为0时,是runtime.Caller执行的位置,line会显示runtime的行号,为1时,会显示执行runtime这个函数上外包的函数的行号,以此类推。

  • pc是uintptr这个返回的是函数指针, 通过runtime.FuncForPC(pc).Name() 可以获取该函数的函数名

  • file是函数所在文件名目录

  • line所在行号

  • ok 是否可以获取到信息

package main

import (
    "fmt"
    "path"
    "runtime"
)

func main() {
    pc, file, line, ok := runtime.Caller(0)
    if !ok {
        fmt.Printf("runtime.Caller() failed\n")
        return
    }
    funcName := runtime.FuncForPC(pc).Name()
    fmt.Println(funcName)
    fmt.Println(file)
    fmt.Println(path.Base(file))
    fmt.Println(line)
}



简单实现

文件目录

----mylogger
|_____console.go
|_____file.go
|_____mylogger.go
----mylogger_test
|_____main.go

通用日志mylogger.go

package mylogger

import (
    "errors"
    "fmt"
    "path"
    "runtime"
    "strings"
)

// 自定义一个日志库
type LogLevel uint16

// Logger 接口
type Logger interface {
    Debug(format string, a ...interface{})
    Info(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
    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"
    }
    return "DEBUG"
}

// 获取执行文件名、函数、行号
func getInfo(skip int) (funcName, fileName string, lineNo int) {
    pc, file, lineNo, ok := runtime.Caller(skip)
    if !ok {
        fmt.Printf("runtime.Caller() failed\n")
        return
    }
    funcName = runtime.FuncForPC(pc).Name()
    fileName = path.Base(file)
    funcName = strings.Split(funcName, ".")[1]
    return

}

控制台打印日志console.go

package mylogger

import (
    "fmt"
    "time"
)

// 往终端写日志相关内容

// Logger 日志结构体
type ConsoleLogger struct {
    Level LogLevel
}

// NewLog 构造函数
func NewConsoleLog(LeveStr string) ConsoleLogger {
    level, err := parseLogLevel(LeveStr)
    if err != nil {
        panic(err)
    }
    return ConsoleLogger{
        Level: level,
    }
}

// 根据日志级别控制日志输出
func (c ConsoleLogger) enable(LogLevel LogLevel) bool {
    return LogLevel >= c.Level
}

// 日志输出格式
func (c ConsoleLogger) log(lv LogLevel, format string, a ...interface{}) {
    if c.enable(lv) {
        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 (c ConsoleLogger) Debug(format string, a ...interface{}) {
    c.log(DEBUG, format, a...)

}

func (c ConsoleLogger) Info(format string, a ...interface{}) {
    c.log(INFO, format, a...)
}

func (c ConsoleLogger) Warning(format string, a ...interface{}) {
    c.log(WARNING, format, a...)
}

func (c ConsoleLogger) Error(format string, a ...interface{}) {
    c.log(ERROR, format, a...)
}

func (c ConsoleLogger) Fatal(format string, a ...interface{}) {
    c.log(FATAL, format, a...)
}

生成日志文件file.go

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 (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
    }
    // 如果当前文件大小 大于等于 日志文件的最大值 就应该返回true
    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 := path.Join(f.filePath, fileInfo.Name())      // 拿到当前日志文件完整路径
    newLogName := fmt.Sprintf("%s.bak%s", logName, nowStr) // 拼接一个日志文件备份的名字
    // 1. 关闭当前的日志文件
    file.Close()
    // 2. 备份一下 rename   xx.log ===> xx.log.bak20220112220435000
    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 new log file failed, err:%v\n", err)
        return nil, err
    }
    // 4. 将打开的新日志文件对象赋值给 f.fileObj
    return fileObj, nil

}

// 初始化日志文件
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
    }
    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
    }
    // 日志文件都已经打开了
    f.fileObj = fileObj
    f.errFileObj = errFileObj
    return nil
}

// 根据日志级别控制日志输出,判断是否记录该日志
func (f *FileLogger) enable(LogLevel LogLevel) bool {
    return LogLevel >= f.Level
}

// 日志输出格式
func (f *FileLogger) log(lv LogLevel, format string, a ...interface{}) {
    if f.enable(lv) {
        msg := fmt.Sprintf(format, a...)
        now := time.Now()
        funcName, fileName, lineNo := getInfo(3)
        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.Format("2006-01-02 15:04:05"), getLogString(lv), 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级别,在err日志文件中再记录一遍
            fmt.Fprintf(f.errFileObj, "[%s] [%s] [%s:%s:%d] %s\n", now.Format("2006-01-02 15:04:05"), getLogString(lv), fileName, funcName, lineNo, msg)
        }
    }
}

func (f *FileLogger) Debug(format string, a ...interface{}) {
    f.log(DEBUG, format, a...)
}

func (f *FileLogger) Info(format string, a ...interface{}) {
    f.log(INFO, format, a...)
}

func (f *FileLogger) Warning(format string, a ...interface{}) {
    f.log(WARNING, format, a...)

}

func (f *FileLogger) Error(format string, a ...interface{}) {
    f.log(ERROR, format, a...)
}

func (f *FileLogger) Fatal(format string, a ...interface{}) {
    f.log(FATAL, format, a...)
}

func (f *FileLogger) Close() {
    f.fileObj.Close()
    f.errFileObj.Close()
}

主函数main.go

package main

import (
    // "time"

    "code.mhg.com/studygo/mylogger"
)

var log mylogger.Logger // 声明一个全局接口

// 测试我们自己写的日志库
func main() {
    // 控制台输出
    // log = mylogger.NewConsoleLog("Debug")
    // 文件输出
    log = mylogger.NewFileLogger("Debug", "./", "mi.log", 1*1024*1024)

    for {
        id := 10010
        name := "理想"

        log.Debug("这是一条Debug日志,id:%d,name:%s", id, name)
        log.Info("这是一条Info日志")
        log.Warning("这是一条Warning日志")
        log.Error("这是一条Error日志")
        log.Fatal("这是一条Fatal日志")
        // time.Sleep(time.Second)
    }
}


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 目录 1.go 各种代码运行 2.go 在线编辑代码运行 3.通过 Gob 包序列化二进制数据 4.使用 ...
    杨言锡阅读 1,125评论 0 1
  • log4j2相对于log4j 1.x有了脱胎换骨的变化,其官网宣称的优势有多线程下10几倍于log4j 1.x和l...
    AaChoxsu阅读 526评论 0 2
  • 一、错误处理 (一) java的异常 1、Java 使用异常来表示错误:(1)异常是一种类 class;(2)本身...
    瀑月流光阅读 270评论 0 0
  • 1、日志的相关概念 日志是一种可以追踪某些软件运行时所发生事件的方法。软件开发人员可以向他们的代码中调用日志记录相...
    PythonAV阅读 392评论 0 0
  • 在我们开发程序后,如果有一些问题需要对程序进行调试的时候,日志是必不可少的,这是我们分析程序问题常用的手段。 日志...
    豆瓣奶茶阅读 18,362评论 0 22