Go红队开发—日志打印优化
文章目录
- 日志
- log
- 输出打印
- 日志控制
- 测试源码
- slog
- 输出打印
- 修改默认等级
- 修改输出目的
- 自定义logger
- 添加日志细节
- 子logger
- 日志颜色修改
- 源码
各位师傅exp与poc编写暂时鸽了,感觉web编程那章节学完自己就能编写,想不出有什么能够学习的地方,因为poc已知基本都是发包收包判断仅此而已,目前是日志章节过后直接开启工具编写,后面exp与poc可能会以工具功能的形式穿插进去。
日志
最终实现的效果:
(这里是json格式化了,你到时候可以不格式化看起来更装b点)
log
输出打印
log日志最容易上手,默认自带时间戳打印日志内容
- 打印
//打印,默认带时间戳
log.Print("log Print")
log.Println("log Println")
log.Printf("%s", "log Printf")
- 日志前缀
//日志前缀(可以用来区别日志级别)
log.SetPrefix("[info] ")
log.Println("info log")
log.SetPrefix("[warn] ")
log.Println("warn log")
- 终止程序
使用日志的时候可能会希望在某些严重错误日志的时候退出程序 - fatal终止
//终止程序
//fatal 终止
log.Fatal("触发日志,终止程序!")
fmt.Println("test log") //终止后并不会执行后面代码
- panic终止
//panic 终止
log.Panic("触发恐慌日志,终止程序!")
fmt.Println("test log") //panic程序崩溃的同时,并不会执行后面代码
日志控制
- 格式控制
log.SetFlags(log.Ldate) //日期(YYYY/MM/DD)
log.Println("test log")
log.SetFlags(log.Ltime) //时间(HH:MM:SS)
log.Println("test log")
log.SetFlags(log.Lmicroseconds) //微秒
log.Println("test log")
log.SetFlags(log.Llongfile) //完整文件路径
log.Println("test log")
log.SetFlags(log.Lshortfile) //文件名+行号
log.Println("test log")
- 日志输出目的
代码中log.Println("123")
是输出到标准输出,让你go run main.go > xx.txt
的时候那个123才会输出到你的xx.txt
文件中
//输出到文件
file, err := os.OpenFile("log.txt", os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
log.Println("打开文件失败:", err)
}
defer file.Close()
log.SetOutput(file)
log.Println("输出到文件中的日志:xxx")
//设置标准输出
log.SetOutput(os.Stdout) //标准输出,这里就是当你 go run main.go > log.txt的时候,log输出的都回到log.txt中
//设置输出标准错误
//log.SetOutput(os.Stderr) //输出标准错误,这个是一直在终端显示的,在终端使用 > 的时候,打印的日志是无法重定向到文件中去
log.Println("123")
- 自定义日志器
file2, err := os.OpenFile("info.log", os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
log.Println("打开文件失败:", err)
}
defer file2.Close()
infoLogger := log.New(file2, "[info] ", log.Ldate)
infoLogger.Println("i am info log")
file3, err := os.OpenFile("warn.log", os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
log.Println("打开文件失败:", err)
}
defer file3.Close()
warnlogger := log.New(file3, "[warn] ", log.Ltime)
warnlogger.Println("i am warn log")
测试源码
// log包测试
func log_test() {
//打印,默认带时间戳
log.Print("log Print")
log.Println("log Println")
log.Printf("%s", "log Printf")
//日志前缀(可以用来区别日志级别)
log.SetPrefix("[info] ")
log.Println("info log")
log.SetPrefix("[warn] ")
log.Println("warn log")
//终止程序
//fatal 终止
// log.Fatal("触发日志,终止程序!")
// fmt.Println("test log") //终止后并不会执行后面代码
//panic 终止
// log.Panic("触发恐慌日志,终止程序!")
// fmt.Println("test log") //panic程序崩溃的同时,并不会执行后面代码
//日志控制
//格式控制
log.SetFlags(log.Ldate) //日期(YYYY/MM/DD)
log.Println("test log")
log.SetFlags(log.Ltime) //时间(HH:MM:SS)
log.Println("test log")
log.SetFlags(log.Lmicroseconds) //微秒
log.Println("test log")
log.SetFlags(log.Llongfile) //完整文件路径
log.Println("test log")
log.SetFlags(log.Lshortfile) //文件名+行号
log.Println("test log")
//日志输出目的
//输出到文件
file, err := os.OpenFile("log.txt", os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
log.Println("打开文件失败:", err)
}
defer file.Close()
log.SetOutput(file)
log.Println("输出到文件中的日志:xxx")
//设置标准输出
log.SetOutput(os.Stdout) //标准输出,这里就是当你 go run main.go > log.txt的时候,log输出的都回到log.txt中
//设置输出标准错误
//log.SetOutput(os.Stderr) //输出标准错误,这个是一直在终端显示的,在终端使用 > 的时候,打印的日志是无法重定向到文件中去
log.Println("123")
//创建自定义日志器
file2, err := os.OpenFile("info.log", os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
log.Println("打开文件失败:", err)
}
defer file2.Close()
infoLogger := log.New(file2, "[info] ", log.Ldate)
infoLogger.Println("i am info log")
file3, err := os.OpenFile("warn.log", os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
log.Println("打开文件失败:", err)
}
defer file3.Close()
warnlogger := log.New(file3, "[warn] ", log.Ltime)
warnlogger.Println("i am warn log")
}
slog
输出打印
- json格式输出
//日志打印
fmt.Println("slog日志:json格式输出")
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
fmt.Println("注意:debug不会输出,没有改默认等级")
logger.Debug("json logger Debug") //没有设置默认等级,现在处于info,所以这里debug不会打印出来
logger.Info("json logger Info")
logger.Warn("json logger Warning")
logger.Error("json logger Error")
- text格式输出
fmt.Println("slog日志:text格式输出")
logger = slog.New(slog.NewTextHandler(os.Stdout, nil))
logger.Info("json logger info")
修改默认等级
等级排序为:debug < info < warn < error
,在log包中一般默认等级都是info,所以不修改打印debug的日志类型是打印不出来的,只能修改。
fmt.Println("更改默认日志等级:")
logger2 := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
logger2.Debug("logger2 Debug")
修改输出目的
-
slog.NewJSONHandler(os.Stdout, nil)
:这里的os.Stdout表示标准输出,可以通过>输出到文件中,其他stderr等等其他自己按照情况切换即可,在log包中详细讲了这几个有什么区别了。 -
slog.NewJSONHandler(os.Stdout, nil)
: 这里第二个参数nil是给自定义log的时候用的,常用的就是可以控制默认等级操作。
(不做修改的默认等级就是info,你打印debug的时候是打印不出来的)
logger3 := slog.New(slog.NewJSONHandler(os.Stdout, nil)) //标准输出
//logger3 := slog.New(slog.NewJSONHandler(os.Stderr, nil)) //标准错误输出
logger3.Debug("logger3 Debug")
自定义logger
意思是说当你通过slog.New控制好一切参数后,将一个实例给到slog默认的logger,那么之后使用slog打印的时候都是使用你那个控制好的参数来打印。
比如这里默认是slog.info
是单纯打印一个info日志而已,我们通过修改后以后得slog.info
打印出来的是json格式
//自定义slog
logger4 := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug, //设置默认等级为debug
}))
slog.SetDefault(logger4) //slog默认设置为我们配置好的logger
slog.Debug("debug test") //现在debug默认的时候就能够打印出来了同时是以json格式打印的
添加日志细节
当我们想要打印日志的时候肯定是希望在发生一些错误的时候我们能够第一时间定位到错误代码以及错误原因,但是普通的打印错误给到的细节可能会比较少,一股脑堆在一起又难看,slog就可以通过添加细节来给到你的log日志。
slog.Group
:意思是分组,很简单看截图就知道,json最明显,就是属性中开一个{}
继续存信息。
看截图:
//为slog日志添加更详细的说明
logger5 := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger5.Info(
"错误代码",
slog.String("函数名", "xxx"),
slog.String("参数值", "xxx xxx"),
slog.Int("返回值", 666),
slog.Group("分组",
slog.String("code信息", "xxxx"),
slog.Int("随便整点", 123),
),
)
子logger
这里我认为非常重要,没看到解释比较好的文章,有看到大佬务必告诉我,我这里就尽量以实用以及意义来讲
- 子logger是继承父logger的,父logger可以理解为我们最初定义的那个logger,子logger创建的意义就是系统在父logger的日志解释上,加上自己独有的日志解释。
以下代码就是实现了database_logger继承了logger6的一些参数设置,然后自己扩展属于自己的一些日志字段解释。
fmt.Println("子logger")
logger6 := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug, //这里多弄点基础性的东西,后面子logger继承的时候就不用设置这么多
}))
database_logger := logger6.With(
//这里就是添加你的log其他解释就行了
slog.String("我是数据库的日志分支", "sql语句报错"),
slog.Int("报错代码code", 111),
)
database_logger.Debug("依旧可以debug日志出来", slog.Int("当然,当你log的时候依旧可以添加更多信息解释你这段log", 123123))
日志颜色修改
结合slog
日志,颜色修改需要借助其他包
下载fatih/color
包,不要下载错了,这里使用的是fatih/color
,我记得有另外一个包也可以修改日志颜色,后续可自行学习,目前用一个就够了
go get github.com/fatih/color
下载完了记得go mod tidy
找到这个包才能用,之前忘记说了
go mod tidy
有了/fatih/color,其实不管是不是日志其实都可以修改颜色,这里最简单一个例子:
fmt.Println(color.CyanString("日志打印完毕"))
结合slog只是为了可控性以及方便性更强,而不是说每次打印都要修改一下颜色这样子,明确了需求下面就开始对slog的改造。
- 实现
slog.Handler
的接口Handle(context.Context, Record) error
- 实现上面接口就要自定义一个结构体,里面
slog.Handler
的接口其实都有默认实现,所以我们很多接口都不用实现,只需要实现一个Handle
就能代表你是slog.Handler
类型了 slog.Handler
:这个是 结构体内嵌(也叫匿名字段)这样 ColorHander 继承了 slog.Handler 的行为,但这里 slog.Handler,它不会自动实现 Handler 方法,你仍然需要手动实现 Handle()colorlogger *log.Logger
:目的是拿*log.Logger的打印方式来打印,也可以用*slog.Logger但是这种的话只能是分级打印,handle方法打印的时候可能需要多写代码了,方便点使用*log.Logger
type ColorHander struct {
slog.Handler
colorlogger *log.Logger
}
Handle
接口实现- 为什么只实现
Handle
?
因为Handle在slog.Handler中我没看到默认实现,所以想要继承slog.Handler作为他的类型的的话一定要实现它,他是一个接口,同时也时日志打印的核心调用函数,我们实现了这个,之后的打印过程中都会调用Handle的参数设置 ColorHander
在函数代码最后进行调用了*log.logger的Println函数进行打印,如果说你当初在结构体给的是*slog.Logger类型的话可能你又要进行分级的一次打印了,同时也时没有必要,其实这里已经将日志完全拆分开给了颜色,然后最后打印即可,甚至你可以使用fmt.Println()进行打印,给一个*log.Logger的话就是为了可控性更加强,我们有一个自定义的外部结构体的logger在handle核心函数里面,控制的时候尽可能少的去动handle核心函数,只需要修改每一个实例化出来的logger就行了。
(不知道讲明白没有)
// 修改打印颜色其实随时可以修改的
// 这里只是实现slog的自定义日志接口,在里面操作修改日志颜色
func (h *ColorHander) Handle(ctx context.Context, r slog.Record) error {
//这里就是实现了slog.handler的接口
level := r.Level.String()
switch r.Level {
//将level更改颜色
case slog.LevelDebug:
level = color.MagentaString(level)
case slog.LevelInfo:
level = color.GreenString(level)
case slog.LevelWarn:
level = color.YellowString(level)
case slog.LevelError:
level = color.RedString(level)
}
//获取日志字段
//开辟空间后续要用,r.NumAttrs()是获取日志字段的个数//开辟空间后续要用,r.NumAttrs()是获取日志字段的个数
logContent := make(map[string]interface{}, r.NumAttrs())
//Attrs是获取日志字段的方法并且返回一个迭代器遍历
//参数给一个匿名函数,这个匿名函数的参数类型和返回值对应上即可自定义遍历日志字段
r.Attrs(func(i slog.Attr) bool { //目的就是获取日志字段,放到logContent里后续使用
logContent[i.Key] = i.Value
return true
})
//map类型的logContent拿到数据后,格式化为json
dataJson, err := json.MarshalIndent(logContent, "", "\t")
if err != nil {
return err
}
//打印日志,因为我们拿到handle函数,就是要修改为自己的日志形式,所以需要进行打印日志
//1.日志中有时间,那么自定义的话自然要将日志的时间格式化
timeStr := r.Time.Format("[15:05:05.000]")
//2.日志的内容也可以在这里修改颜色,上面只是对level进行了颜色修改
message := color.CyanString(r.Message)
//3.设置完成后就可以打印了属于你自己的日志了
//这里的h就派上用场了,其实不给那个colorlogger也行,只是你在使用的时候无法支持log.Logger的用法
//没有colorlogger 的话,我们外部创建logger的单个示例的时候就无法应用到全部身上了
h.colorlogger.Println(timeStr, level, message, string(dataJson))
return nil
}
源码
想必看到日志打印出来的那一刻,是十分的优雅
// 实现日志颜色修改
type ColorHander struct {
//这个是 结构体内嵌(也叫匿名字段)这样 ColorHander 继承了 slog.Handler 的行为,但这里 slog.Handler 只是一个接口,它不会自动实现 Handler 方法,你仍然需要手动实现 Handle()
slog.Handler
//用来实现slog.handler的接口,因为接口只有一个,实现了就能作为*log.handler传参了
//目的是拿*log.Logger的打印方式来打印,也可以用*slog.Logger但是这种的话只能是分级打印,handle方法打印的时候可能需要多写代码了,方便点使用*log.Logger
colorlogger *log.Logger
}
// 修改打印颜色其实随时可以修改的
// 这里只是实现slog的自定义日志接口,在里面操作修改日志颜色
func (h *ColorHander) Handle(ctx context.Context, r slog.Record) error {
//这里就是实现了slog.handler的接口
level := r.Level.String()
switch r.Level {
//将level更改颜色
case slog.LevelDebug:
level = color.MagentaString(level)
case slog.LevelInfo:
level = color.GreenString(level)
case slog.LevelWarn:
level = color.YellowString(level)
case slog.LevelError:
level = color.RedString(level)
}
//获取日志字段
//开辟空间后续要用,r.NumAttrs()是获取日志字段的个数//开辟空间后续要用,r.NumAttrs()是获取日志字段的个数
logContent := make(map[string]interface{}, r.NumAttrs())
//Attrs是获取日志字段的方法并且返回一个迭代器遍历
//参数给一个匿名函数,这个匿名函数的参数类型和返回值对应上即可自定义遍历日志字段
r.Attrs(func(i slog.Attr) bool { //目的就是获取日志字段,放到logContent里后续使用
logContent[i.Key] = i.Value
return true
})
//map类型的logContent拿到数据后,格式化为json
dataJson, err := json.MarshalIndent(logContent, "", "\t")
if err != nil {
return err
}
//打印日志,因为我们拿到handle函数,就是要修改为自己的日志形式,所以需要进行打印日志
//1.日志中有时间,那么自定义的话自然要将日志的时间格式化
timeStr := r.Time.Format("[15:05:05.000]")
//2.日志的内容也可以在这里修改颜色,上面只是对level进行了颜色修改
message := color.CyanString(r.Message)
//3.设置完成后就可以打印了属于你自己的日志了
//这里的h就派上用场了,其实不给那个colorlogger也行,只是你在使用的时候无法支持log.Logger的用法
//没有colorlogger 的话,我们外部创建logger的单个示例的时候就无法应用到全部身上了
h.colorlogger.Println(timeStr, level, message, string(dataJson))
return nil
}
func slog_colorTest() {
fmt.Println("-----------------------slog_colorTest------------------------")
myColorLogger := slog.New(&ColorHander{
Handler: slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}),
colorlogger: log.New(os.Stdout, "", 0),
})
myColorLogger.Debug("debug日志", slog.String("code信息", "xxxx"), slog.Int("随便整点", 123))
myColorLogger.Info("info日志", slog.String("code信息", "xxxx"), slog.Int("随便整点", 123))
myColorLogger.Warn("warn日志", slog.String("code信息", "xxxx"), slog.Int("随便整点", 123))
myColorLogger.Error("error日志", slog.String("code信息", "xxxx"), slog.Int("随便整点", 123))
}
func main() {
slog_colorTest()
}