用选项模式实现一个简单的golang日志库

前言

因为看了掘金上的一篇文章,然后对其进行了一些修改,文章末尾会给出该文章的地址链接。同时也作为练习,提升自己的golang代码的编写能力。

为什么用选项模式?

选项模式是go语言开发中经常会使用到的一种设计模式。能够方便的进行配置的初始化。如果对选项模式不了解可以从文末参考资料找到选项模式的介绍。

该日志库都包含什么?
  1. 根据自定义时间来间隔打印日志库
  2. 并行对日志库进行分割打印
  3. 可以选择是否打印至控制台
  4. 日志的优雅关闭
  5. 日志颜色的自定义
  6. 日志级别可扩展
  7. 日志堆栈输出但并不准确(如有需要可以自行调整)
主逻辑代码
package logger

import (
	"fmt"
	"os"
	"os/signal"
	"runtime/debug"
	"strings"
	"syscall"
	"time"
)

const (
	MsgChanLen        = 10000
	MsgBufMaxLen      = 100
	FlushTimeInterval = time.Second // 日志刷盘周期
)

type Logger struct {
	f          *os.File
	bufMessage []*message
	chanMes    chan *message
	color      *Color
	console    bool
}

type message struct {
	level     Level
	content   string
	currentAt time.Time
}

// Opt 选项模式, 可以自主选择文件进行日志打印,可以自主选择日志颜色,默认黑色,可以自主选择是否打印至控制台输出
type Opt func(*Logger)

func WithFile(f *os.File) Opt {
	return func(l *Logger) {
		l.f = f
	}
}

func WithColor(c *Color) Opt {
	return func(l *Logger) {
		l.color = c
	}
}

func WithConsole(b bool) Opt {
	return func(l *Logger) {
		l.console = b
	}
}

func NewLogger(opts ...Opt) *Logger {
	f, err := os.OpenFile("log.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777)
	if err != nil {
		panic(fmt.Errorf("openFile method fail! err: %v", err))
	}
	l := &Logger{
		f:          f,
		bufMessage: make([]*message, 0, MsgBufMaxLen),
		chanMes:    make(chan *message, MsgChanLen),
		color:      NewColor(BLACK),
		console:    false,
	}
	for _, opt := range opts {
		opt(l)
	}
	// 监听日志打印情况
	go l.listenFlush()

	return l
}

func (l *Logger) Info(content string) {
	l.chanMes <- &message{
		content:   content,
		level:     INFO,
		currentAt: time.Now(),
	}
}

func (l *Logger) Debug(content string) {
	l.chanMes <- &message{
		content:   content,
		level:     DEBUG,
		currentAt: time.Now(),
	}
}

func (l *Logger) Error(err error) {
	l.chanMes <- &message{
		content:   err.Error(),
		level:     ERROR,
		currentAt: time.Now(),
	}
}

// formatMes 格式化时间
func (l *Logger) formatMes(mes *message) string {
	var build strings.Builder
	build.WriteString(mes.currentAt.Format("[2006-01-02 15:04:05]"))
	build.WriteString("   ")
	build.WriteString(mes.level.status())
	build.WriteString(strings.Split(string(debug.Stack()), "\n")[2])
	build.WriteString("   ")
	build.WriteString(l.color.addColor(mes.content))
	build.WriteString("\n")
	return build.String()
}

// listenFlush 开启日志监听
func (l *Logger) listenFlush() {
	// 利用信号阻塞实现优雅关闭
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	ticker := time.NewTicker(FlushTimeInterval)
	for {
		select {
		case mes := <-l.chanMes:
			l.bufMessage = append(l.bufMessage, mes)
			if len(l.bufMessage) == MsgBufMaxLen {
				fmt.Println("缓冲区容量达到上限,进行日志刷盘")
				l.do()
			}
		case <-ticker.C:
			fmt.Println("每隔一秒,进行日志刷盘")
			l.do()
		case <-quit:
			fmt.Println("收到结束信息,日志关闭,优雅退出")
			l.do()
			l.f.Close()
		}
	}
}

// do 处理日志情况
func (l *Logger) do() (err error) {
	// 整合缓冲区内容
	var build strings.Builder
	for _, mes := range l.bufMessage {
		build.WriteString(l.formatMes(mes))
	}
	content := build.String()
	// 如果无内容直接返回
	if content == "" {
		return
	}

	l.bufMessage = make([]*message, 0, MsgBufMaxLen)

	// 刷新缓冲区进入文件
	err = l.batchFlush(content)
	// 判断是否控制台输出
	if l.console {
		err = l.batchConsole(content)
	}
	return
}

// batchFlush 将缓冲区内容写入文件
func (l *Logger) batchFlush(content string) (err error) {
	_, err = l.f.WriteString(content)
	if err != nil {
		fmt.Println("write to file failed! ,err = ", err)
		return
	}
	fmt.Printf("[%v] write to file success!", time.Now().String())
	return
}

// batchConsole 将缓冲区内容打印至控制台
func (l *Logger) batchConsole(content string) (err error) {
	_, err = fmt.Fprintln(os.Stdout, content)
	if err != nil {
		fmt.Println("stdout to console failed! ,err = ", err)
	}
	return
}

Color代码
package logger

import "fmt"

const (
	BLACK = iota + 30
	RED
	GREEN
	YELLOW
	BLUE
	PURPLE
	CYAN
	WHITE
)

type Color struct {
	color int
}

// NewColor 实例化一个Color对象
func NewColor(color int) *Color {
	return &Color{color: color}
}

func Black() *Color {
	return NewColor(BLACK)
}

func Cyan() *Color {
	return NewColor(CYAN)
}

func White() *Color {
	return NewColor(WHITE)
}

func Blue() *Color {
	return NewColor(BLUE)
}

func Purple() *Color {
	return NewColor(PURPLE)
}

// AddColor 对字符串进行加色输出
func (c *Color) addColor(str string) string {
	return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", c.color, str)
}

Level代码
package logger

const (
	INFO Level = iota
	DEBUG
	WARN
	ERROR
	FATAL
)

type Level int

var level = map[Level]string{
	INFO:  "[info]",
	DEBUG: "[debug]",
	WARN:  "[warn]",
	ERROR: "[error]",
	FATAL: "[fatal]",
}

type Lvl struct {
	level int
}

// 根据日志等级来获取到对应的状态表示
func (l Level) status() string {
	if st, ok := level[l]; ok {
		return st
	}
	return "[NotKnow]"
}

测试

package logger

import (
	"fmt"
	"math/rand"
	"sync"
	"testing"
	"time"
)

// 使用waitGroup来控制退出
var g sync.WaitGroup

func TestLogger_Info(t *testing.T) {
	logger := NewLogger(WithColor(Cyan()), WithConsole(true))
	g.Add(100)
	for i := 0; i < 100; i++ {
		go func() {
			n := rand.Intn(1000)
			logger.Info(fmt.Sprintf("hello, diyLogger, 暂停时间为: %d", n))
			time.Sleep(time.Duration(n) * time.Millisecond)
			g.Done()
		}()
	}
	g.Wait()
}

效果

日志文件效果

在这里插入图片描述

控制台效果

在这里插入图片描述
具体打印情况可以自行修改.

参考文章:

130行代码优雅实现golang日志库

option函数选项模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

捶捶自己

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值