前言
因为看了掘金上的一篇文章,然后对其进行了一些修改,文章末尾会给出该文章的地址链接。同时也作为练习,提升自己的golang
代码的编写能力。
为什么用选项模式?
选项模式是go
语言开发中经常会使用到的一种设计模式。能够方便的进行配置的初始化。如果对选项模式不了解可以从文末参考资料找到选项模式的介绍。
该日志库都包含什么?
- 根据自定义时间来间隔打印日志库
- 并行对日志库进行分割打印
- 可以选择是否打印至控制台
- 日志的优雅关闭
- 日志颜色的自定义
- 日志级别可扩展
- 日志堆栈输出但并不准确(如有需要可以自行调整)
主逻辑代码
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()
}
效果
日志文件效果
控制台效果
具体打印情况可以自行修改.