Logrus 集成 color 库实现自定义日志颜色输出字符原理

问题背景

下列代码实现了使用 Logurs 日志框架输出日志时根据级别不同,使用对应的自定义颜色进行输出。那么思考下代码的逻辑是怎么实现的呢?

效果如下:

在这里插入图片描述

代码如下:

import ("fmt""github.com/sirupsen/logrus""github.com/fatih/color"
)func main() {logger := logrus.New()logger.Formatter = &CustomTextFormatter{ForceColors:   true,ColorInfo:     color.New(color.FgBlue),ColorWarning:  color.New(color.FgYellow),ColorError:    color.New(color.FgRed),ColorCritical: color.New(color.BgRed, color.FgWhite),}logger.Info("This is an info message")   // 输出蓝色的信息日志logger.Warn("This is a warning message") // 输出黄色的警告日志logger.Error("This is an error message") // 输出红色的错误日志logger.Fatal("This is a critical message") // 输出带有红色背景和白色文本的严重日志logger.Panic("This is a critical message with panic") // 输出带有红色背景和白色文本的严重日志,并引发 panic
}// 自定义格式化器,继承自 logrus.TextFormatter
type CustomTextFormatter struct {logrus.TextFormatterForceColors   boolColorInfo     *color.ColorColorWarning  *color.ColorColorError    *color.ColorColorCritical *color.Color
}// 格式化方法,用于将日志条目格式化为字节数组
func (f *CustomTextFormatter) Format(entry *logrus.Entry) ([]byte, error) {if f.ForceColors {switch entry.Level {case logrus.InfoLevel:f.ColorInfo.Println(entry.Message) // 使用蓝色打印信息日志case logrus.WarnLevel:f.ColorWarning.Println(entry.Message) // 使用黄色打印警告日志case logrus.ErrorLevel:f.ColorError.Println(entry.Message) // 使用红色打印错误日志case logrus.FatalLevel, logrus.PanicLevel:f.ColorCritical.Println(entry.Message) // 使用带有红色背景和白色文本的样式打印严重日志default:f.PrintColored(entry)}return nil, nil} else {return f.TextFormatter.Format(entry)}
}// 自定义方法,用于将日志条目以带颜色的方式打印出来
func (f *CustomTextFormatter) PrintColored(entry *logrus.Entry) {levelColor := color.New(color.FgCyan, color.Bold) // 定义蓝色和粗体样式levelText := levelColor.Sprintf("%-6s", entry.Level.String()) // 格式化日志级别文本msg := levelText + " " + entry.Messageif entry.HasCaller() {msg += " (" + entry.Caller.File + ":" + strconv.Itoa(entry.Caller.Line) + ")"  // 添加调用者信息}fmt.Fprintln(color.Output, msg) // 使用有颜色的方式打印消息到终端
}

了解转义序列的颜色格式字符串

假如我们在代码中执行以下代码,那么你将会在控制台中看到带有红色的 “Hello World”

str := fmt.Sprintf("\033[31m%s\033[0m", "Hello World")
fmt.Println(str)

这是因为我们使用 fmt.Sprintf 方法生成了一个带有转义序列的颜色格式字符串。

在这个格式字符串中,%s 是一个占位符,表示将要插入的字符串。而 \033[31m\033[0m 是转义序列,用于在终端中设置文本的颜色,\033[31m 用于设置文本的颜色为红色,\033[0m 则是用于重置文本的颜色为默认值。

通过将实际的字符串插入到 %s 的位置,然后打印输出这个格式化字符串,可以在终端中实现相应部分带有颜色显示的效果。

**注:**该格式字符串的使用依赖于终端对 ANSI 转义序列的支持,因此可能在某些环境或终端下无效。

调用链

首先,我们的代码中定义了一个继承 logrus.TextFormatter 的结构体 CustomTextFormatter,并实现Format方法来自定义日志的输出格式。还将 Logrus 实例的 Formatter 设置为自定义格式化器 CustomTextFormatter

logger := logrus.New()
logger.Formatter = &CustomTextFormatter{ForceColors:   true,ColorInfo:     color.New(color.FgBlue),ColorWarning:  color.New(color.FgYellow),ColorError:    color.New(color.FgRed),ColorCritical: color.New(color.BgRed, color.FgWhite),
}logger.Info("This is an info message")
logger.Warn("This is a warning message")
logger.Error("This is an error message")
logger.Fatal("This is a critical message")

当调用 logger.Info() 时,会调用到 Logger.Log() 方法输出日志。Logger.Log() 首先通过调用 logger.IsLevelEnabled(level) 方法检查指定的日志级别是否启用。如果未启用,则直接返回,不执行后续的日志记录操作。

如果指定的日志级别是启用的,接下来会创建一个新的日志条目 entry,通过调用 logger.newEntry() 方法实现。

然后,通过调用 entry.Log(level, args...) 方法,将日志级别和参数传递给日志条目进行记录。这表示具体的日志记录操作和格式化输出由 entry.Log 方法负责执行。

最后,通过调用 logger.releaseEntry(entry) 方法释放和回收该日志条目的资源。

func (logger *Logger) Log(level Level, args ...interface{}) {if logger.IsLevelEnabled(level) {entry := logger.newEntry()entry.Log(level, args...)logger.releaseEntry(entry)}
}

entry.Log 中,首先使用 entry.Dup() 方法复制当前 Entry 实例的 LoggerTimeContexterr 创建一个新的 Entry 实例,并返回该新实例的指针,并将新实例的时间设置为当前时间,级别和消息设置为传入的参数值。这个新实例被称为 newEntry

后续,通过调用 newEntry.write() 方法,将新实例 newEntry 的内容写入缓冲区 buffer 中。

最后,释放新实例 newEntry 的缓冲区,检查日志级别是否需要触发 panic,并在需要触发 panic 时抛出新实例 newEntry

func (entry *Entry) log(level Level, msg string) {var buffer *bytes.BuffernewEntry := entry.Dup()if newEntry.Time.IsZero() {newEntry.Time = time.Now()}newEntry.Level = levelnewEntry.Message = msgnewEntry.Logger.mu.Lock()reportCaller := newEntry.Logger.ReportCallerbufPool := newEntry.getBufferPool()newEntry.Logger.mu.Unlock()if reportCaller {newEntry.Caller = getCaller()}newEntry.fireHooks()buffer = bufPool.Get()defer func() {newEntry.Buffer = nilbuffer.Reset()bufPool.Put(buffer)}()buffer.Reset()newEntry.Buffer = buffernewEntry.write()newEntry.Buffer = nil// To avoid Entry#log() returning a value that only would make sense for// panic() to use in Entry#Panic(), we avoid the allocation by checking// directly here.if level <= PanicLevel {panic(newEntry)}
}

newEntry.write() 方法就会调用 entry.Logger.Formatter.Format(entry) 方法将当前 Entry 实例格式化为字节流。注意,这里使用的 Formatter 是在创建 Logger 时指定的日志格式化器,也就是我们自定义的 CustomTextFormatter

func (entry *Entry) write() {entry.Logger.mu.Lock()defer entry.Logger.mu.Unlock()serialized, err := entry.Logger.Formatter.Format(entry)if err != nil {fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)return}if _, err := entry.Logger.Out.Write(serialized); err != nil {fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)}
}

CustomTextFormatter.Format() 方法我们已经实现了,根据日志级别选择设置对应的自定义颜色并打印日志消息。如果ForceColorstrue,则使用定义的颜色样式来打印日志;否则,调用父类的TextFormatter.Format方法进行默认的格式化操作:

func (f *CustomTextFormatter) Format(entry *logrus.Entry) ([]byte, error) {if f.ForceColors {switch entry.Level {case logrus.InfoLevel:f.ColorInfo.Println(entry.Message)case logrus.WarnLevel:f.ColorWarning.Println(entry.Message)case logrus.ErrorLevel:f.ColorError.Println(entry.Message)case logrus.FatalLevel, logrus.PanicLevel:f.ColorCritical.Println(entry.Message)default:f.PrintColored(entry)}return nil, nil} else {return f.TextFormatter.Format(entry)}
}

当在调用 f.ColorInfo.Println() 中,会首先调用 c.Set() 方法,将当前颜色设置为控制台输出的颜色。接着使用 defer c.unset() 语句,确保在方法执行完毕后恢复控制台输出的默认颜色。这样可以避免影响后续输出的颜色。

最后,通过调用 fmt.Fprintln(Output, a...) 方法,格式化并输出参数 a... 的内容到指定的输出位置。

func (c *Color) Println(a ...interface{}) (n int, err error) {c.Set()defer c.unset()return fmt.Fprintln(Output, a...)
}

而到 Color 结构体的 Set 方法中会先通过调用 c.isNoColorSet() 方法检查是否设置了 noColor(不使用颜色)选项。如果设置了 noColor 选项,则直接返回 Color 结构体本身,不做任何操作。

如果未设置 noColor 选项,则调用 c.format() 方法生成带有转义序列的颜色格式字符串。然后使用 fmt.Fprint 方法将该颜色格式字符串输出到 Output(继承自 log 包)中。

// Set sets the SGR sequence.
func (c *Color) Set() *Color {if c.isNoColorSet() {return c}fmt.Fprint(Output, c.format())return c
}

Color 结构体的 format 方法会首先调用 c.sequence() 方法获取当前颜色序列的 ANSI 转义序列值。然后使用 fmt.Sprintf 方法将转义序列插入到格式化字符串中,生成完整的颜色格式字符串。

func (c *Color) format() string {return fmt.Sprintf("%s[%sm", escape, c.sequence())
}

其中,escape 是一个字符转义常量,表示 ANSI 转义序列的起始字符,通常为 \033c.sequence() 方法返回的是字符串类型的 ANSI 转义序列,例如 "31" 表示红色文本。最后返回的是带有转义序列的颜色格式字符串,例如 \033[31m 表示设置文本颜色为红色。

最终调用链再执行到 fmt.Fprintln(Output, a...) 方法的 return fmt.Fprintln(Output, a...) 将格式化后的数据输出到指定的 io.Writer 接口上,并返回输出的字节数和可能的错误:

func Fprintln(w io.Writer, a ...any) (n int, err error) {p := newPrinter()p.doPrintln(a)n, err = w.Write(p.buf)p.free()return
}

整个原理就是这些!!!
感谢关注!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/148566.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

ARM汇编与C言语的混合编程

1. C言语如何与汇编进行交互 有些时候&#xff0c;我们需要在汇编代码中调用C代码&#xff0c;或者说C代码中调用汇编代码。 那么&#xff0c;汇编调用C代码&#xff0c;或者C代码调用汇编函数&#xff0c;他们的函数参数、返回值是如何传递的&#xff1f; 对应ARM架构来说&…

angularjs开发环境搭建

Angularjs是一个前端页面应用开发框架&#xff0c;其使用TypeScript作为开发语言&#xff0c;Angularjs的特性包括&#xff0c;使用组件、模板以及依赖注入的开发框架构建可扩展的web应用&#xff0c;使用易于集成的类库支持页面路由、页面表单、前后端接口交互等各种不同特性&…

RabbitMQ-主题模式

接上文 RabbitMQ-发布订阅模式和路由模式 1 主题模式 #通配符 代表0个或多个。*通配符 代表 1个或多个 进行测试&#xff0c;修改配置文件 Configuration public class RabbitConfiguration {Bean("topicExchange") //这里使用预置的Topic类型交换机public Exchan…

Android Studio实现简易计算器(带横竖屏,深色浅色模式,更该按钮颜色,selector,style的使用)

目录 前言 运行结果&#xff1a; 运行截屏&#xff08;p50e&#xff09; apk文件 源码文件 项目结构 总览 MainActivity.java drawable 更改图标的方法&#xff1a; blackbutton.xml bluebuttons.xml greybutton.xml orangebuttons.xml whitebutton.xml layout 布…

嵌入式Linux应用开发-驱动大全-同步与互斥③

嵌入式Linux应用开发-驱动大全-同步与互斥③ 第一章 同步与互斥③1.4 Linux锁的介绍与使用1.4.1 锁的类型1.4.1.1 自旋锁1.4.1.2 睡眠锁 1.4.2 锁的内核函数1.4.2.1 自旋锁1.4.2.2 信号量1.4.2.3 互斥量1.4.2.4 semaphore和 mutex的区别 1.4.3 何时用何种锁1.4.4 内核抢占(pree…

2023年中国体育赛事行业现状及趋势分析:体育与科技逐步融合,推动产业高质量发展[图]

体育赛事运营是指组织体育赛事或获取赛事版权&#xff0c;并进行赛事推广营销、运营管理等一系列商业运作的运营活动。体育赛事运营相关业务主要包括赛事运营与营销、赛事版权运营两个部分。 体育赛事运营行业分类 资料来源&#xff1a;共研产业咨询&#xff08;共研网&#x…

MySQL面试题合集

MySQL面经知识整理 文章目录 MySQL面经知识整理一、查询相关1.什么是MySQL的连接查询&#xff0c;左连接&#xff0c;右连接&#xff0c;内外连接2.SQL慢查询优化的方法3.大表查询如何优化 二、索引相关1.在MySQL中,可以通过哪些命令来查看查询是否使用了索引2.MySQL的最左匹配…

实验三十四、串联型稳压电路参数的选择

一、题目 电路如图1所示。已知输入电压为 50 Hz 50\,\textrm{Hz} 50Hz 的正弦交流电&#xff0c;来源于电源变压器副边&#xff1b;输出电压调节范围为 5 ∼ 20 V 5\sim20\,\textrm V 5∼20V&#xff0c;满载为 0.5 A 0.5\,\textrm A 0.5A&#xff1b; C 3 C_3 C3​ 为消振…

在排序数组中查找元素的第一个和最后一个位置

给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。 示例 1&#xff1a…

结构和基本尺寸

声明 本文是学习GB-T 586-2015 船用法兰铸钢止回阀. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了法兰连接尺寸和密封面按 CB/T 4196、GB/T 2501 的船用法兰铸钢止回阀(以下简 称止回阀)的分类和标记、要求、试验方法、检验规…

使用Java操作Redis

要在Java程序中操作Redis可以使用Jedis开源工具。 一、jedis的下载 如果使用Maven项目&#xff0c;可以把以下内容添加到pom中 <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId>…

【LeetCode热题100】--114.二叉树展开为链表

114.二叉树展开为链表 方法一&#xff1a;对二叉树进行先序遍历&#xff0c;得到各个节点被访问到的顺序&#xff0c;利用数组存储下来&#xff0c;然后在先序遍历之后更新每个节点的左右节点的信息&#xff0c;将二叉树展开为链表 /*** Definition for a binary tree node.* …

Vue+ElementUI实现动态树和表格数据的分页模糊查询

目录 前言 一、动态树的实现 1.数据表 2.编写后端controller层 3.定义前端发送请求路径 4.前端左侧动态树的编写 4.1.发送请求获取数据 4.2.遍历左侧菜单 5.实现左侧菜单点击展示右边内容 5.1.定义组件 5.2.定义组件与路由的对应关系 5.3.渲染组件内容 5.4.通过动态…

FFmpeg 命令:从入门到精通 | ffmpeg filter(过滤器 / 滤镜)

FFmpeg 命令&#xff1a;从入门到精通 | ffmpeg filter&#xff08;过滤器 / 滤镜&#xff09; FFmpeg 命令&#xff1a;从入门到精通 | ffmpeg filter&#xff08;过滤器 / 滤镜&#xff09;ffmpeg fliter 基本内置变量视频裁剪文字水印图片水印画中画视频多宫格处理 FFmpeg 命…

希尔排序(C++实现)

文章目录 前言1. 基础概念2. 动图演示3. 代码实现4. 排序过程5. 效率分析6. 总结 前言 上篇文章讲了直接插入排序算法。 首先&#xff0c;在待排序的数组中&#xff0c;元素本身就是有序的情况下&#xff0c;就不需要移动任何元素&#xff0c;所以直接插入排序最好情况时间复…

进程调度的时机,切换与过程以及方式

1.进程调度的时机 进程调度&#xff08;低级调度〉&#xff0c;就是按照某种算法从就绪队列中选择一个进程为其分配处理机。 1.需要进行进程调度与切换的情况 1.当前运行的进程主动放弃处理机 进程正常终止运行过程中发生异常而终止进程主动请求阻塞&#xff08;如等待l/O)…

数据结构与算法-顺序表

数据结构与算法 &#x1f388;1.线性表&#x1f50e;1.1基本操作&#x1f50e;1.2线性表的存储结构 &#x1f388;2.线性表的顺序表示和实现&#x1f50e;2.1线性表的顺序存储表示&#x1f52d;2.1.1静态顺序表&#x1f52d;2.1.2动态顺序表 &#x1f50e;2.2顺序表基本操作的实…

MYSQL8解压版 windows 主从部署步骤及配置(包含配置文件,教程文件,免积分下载)

MYSQL8解压版 windows 主从部署步骤及配置 一.安装MSYQL 这里只讲大概,详细步骤、my.ini文件、安装包等会在页尾文件中(正常情况按首个mysql安装,只是名字有区别) 1.主库my.ini配置 [mysqld] #典型的值是5-6GB(8GB内存)&#xff0c;8-11GB(16GB内存), 20-25GB(32GB内存)&…

阿里云对象存储OSS SDK的使用

官方文档 https://help.aliyun.com/zh/oss/developer-reference/java 准备工作 windows安装好JDK&#xff0c;这里使用JDK1.8为例 windows安装好IDEA&#xff0c;这里使用IDEA2022 登录阿里云控制台&#xff0c;通过免费试用OSS或开通OSS 步骤 配置访问凭证 有临时和长期…

【Go】go-es统计接口被刷数和ip访问来源

go-es模块统计日志中接口被刷数和ip访问来源 以下是使用go的web框架gin作为后端&#xff0c;展示的统计页面 背景 上面的数据来自elk日志统计。因为elk通过kibana进行展示&#xff0c;但是kibana有一定学习成本且不太能满足定制化的需求&#xff0c;所以考虑用编程的方式…