前言&引入

一个好的log模块可以帮助我们排错,分析,统计

一般来说log中需要有时间、栈信息(比如说文件名行号等),这些东西一般某些底层log模块已经帮我们做好了。但在业务中还有很多我们需要记录的信息,比如说:在web开发中,如果我们接收到一条request,我们可能需要执行很多操作,最基本的:

  • 请求数据是要记录的
  • response也是要记录的

如果仅仅只有这两条的话我们实际上是可以将消息放到一行来展示,但更复杂的情况是也可能还需要记录某些其他的信息,比如说我们在这次请求中将某个消息放入了消息队列,我们可能需要将这个消息是否放置成功,内容是什么,等等记录下来。如果分行记录的话当出现问题需要排查的话可能会十分麻烦,因为线上的环境一般是并发的,我们无法保证同一个请求中的日志每行都挨在一起,所以我们一般需要一个requestId来区分哪些日志是同一个请求所产生的。所以我们可能需要这样的请求处理函数:

func HandleRequest(requestId string, requestData []byte) (response []byte) {
log.Info(requestId, requestData)
...
log.Info(requestId, "do something: A")
...
log.Info(requestId, "do something: B")
...
log.Info(requestId, response)
...
}

但这样是不是很麻烦!每次打印日志都需要额外的手动记录requestId,我们需要有个通用的东西统一记录requestId,然后只需要将msg作为参数放置进去就行了。

那么我们可能会想到一个解决办法:每个Request都作为一个结构体,这个结构体包含了一个prefix字段,用来存储像requestId这样的需要预置的前缀,那么这个结构体可能看起来是这样的:

type Request struct {
Header []byte
Body []byte
Method []byte
Url []byte
...
prefix string
} func (r *Request) Info(msg []byte) {
log.Info(r.prefix, msg)
} func (r *Request) SetPrefix(prefix string) {
r.prefix = prefix
}

那么我们前面的请求处理函数可能就像这样:

func HandleRequest(r *Request) (response []byte) {
r.Info(requestData)
...
r.Info("do something: A")
...
r.Info("do something: B")
...
r.Info(response)
...
}

到这里似乎大功就告成了,但新的问题来了,因为项目中用到了http2.0,一个连接可以处理多个请求,你的老大希望每个连接都要记录日志,且能正确区分不同的连接。这时候你可能想都没想就给连接结构体Conn加上了prefix字段,然后给Conn加上了Info等记录方法,但聪明的你忽然发现自己似乎是在做一些重复的工作,为何不把日志抽离出来?于是就像这样:

// r.go
type PrefixLog struct {
prefix string
} func NewPrefixLog(prefix string) (pl *PrefixLog){
return $PrefixLog{prefix}
} func (pl *PrefixLog) Info(msg []byte){
Log.Info(pl.prefix, msg) // 假设这里行号是30
} type Request struct {
Header []byte
Body []byte
Method []byte
Url []byte
...
*PrefixLog
} type Conn struct {
requestCount uint32
*PrefixLog
}
...

这次基本大功告成!但似乎新的问题又来了,假如为了更方便的排错,我们在日志需要保存log的文件名行号信息的话,上面这种形式就有问题了,因为通过这种方式调用的话所有的日志的文件名和行号都是相同的: file_name: r.go line:30,这该咋办呢?

正文

frp中的log模块相对简单,其封装了beego的log模块,主要逻辑写在utils/log文件中,来分析一下该文件。

全局变量之Log

import (
"github.com/fatedier/beego/logs"
) // Log is the under log object
var Log *logs.BeeLogger

这个Log变量是frp中log模块的核心,几乎所有(或者说就是所有)的日志都是由这个Log变量来负责操作的。

初始化之init函数

func init() {
Log = logs.NewLogger(200)
Log.EnableFuncCallDepth(true)
Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
}

这个init函数则初始化了Log对象,注意Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)这句,大体上就是:我们的程序可以说是由一个一个的函数组成,这些函数之间相互调用,每调用一个函数就进行了一次入栈操作,当某个函数执行完就执行了出栈操作,而loggerFuncCallDepth则表示要访问的栈的位置。

关于calldepth

那这个东西有啥用呢?我们知道我们打印日志的时候有的时候希望能够在日志中输出执行log的文件以及行号信息,拿go标准库log举个例子:

// main 文件
func a() {
...
b("hell0") // 假如该行行号为10
...
} func wtf(msg string) {
...
msg = "[WTF!!]: " + msg
log.Printf(msg) // 假如该行行号为21
...
} func main() {
a()
}
// 标准库log中的Printf方法,注意其内部调用了Output方法,且第一个参数为2
func Printf(format string, v ...interface{}) {
std.Output(2, fmt.Sprintf(format, v...))
} // 这是真正执行了打印的方法
func (l *Logger) Output(calldepth int, s string) error {
...
}

这里函数的调用顺序是main -> a -> wtf -> log.Printf -> Output,可以说这是一个深度为5的栈,calldepth为0表示栈顶,也就是Output对应的栈空间,1则表示log.Printf对应的栈空间,2表示wtf对应的栈空间,3表示wtf......以此类推。因为log模块设置的callpath是2,也就是假如我们设置了Llongfile或者Lshortfile标识符的时候输出的文件名是main,行号为21,假如我们设置callpath为3的话,输出的文件名依然是main但行号则变为了10。

打印函数

这里打印函数就拿Info来说明吧

func Info(format string, v ...interface{}) {
Log.Info(format, v...)
}

可以看到Info函数实际上就是调用了Log.Info方法,Log.Info做了很多关于并发控制,格式输出,buffer写入的操作,但其最主要就是做了“将我们要打印的文字输出出来”这个操作。

log文件中唯一的一个结构体: PrefixLogger

type PrefixLogger struct {
prefix string
allPrefix []string
} func (pl *PrefixLogger) AddLogPrefix(prefix string) {
if len(prefix) == 0 {
return
} pl.prefix += "[" + prefix + "] "
pl.allPrefix = append(pl.allPrefix, prefix)
} // 同样,这里也仅仅列出PrefixLogger的Info方法
func (pl *PrefixLogger) Info(format string, v ...interface{}) {
Log.Info(pl.prefix+format, v...)
}

PrefixLogger实际上就是一个具有前缀功能的很简单的结构体。

frp源码剖析-frp中的log模块的更多相关文章

  1. frp源码剖析-frp中的mux模块

    前言 frp几乎所有的连接处理都是构建在mux模块之上的,重要性不必多说,来看一下这是个啥吧 ps: 安装方法 go get "github.com/fatedier/golib/net/m ...

  2. 跨站请求伪造(csrf),django的settings源码剖析,django的auth模块

    目录 一.跨站请求伪造(csrf) 1. 什么是csrf 2. 钓鱼网站原理 3. 如何解决csrf (1)思路: (2)实现方法 (3)实现的具体代码 3. csrf相关的装饰器 (1)csrf_p ...

  3. Django对中间件的调用思想、csrf中间件详细介绍、Django settings源码剖析、Django的Auth模块

    目录 使用Django对中间件的调用思想完成自己的功能 功能要求 importlib模块介绍 功能的实现 csrf中间件详细介绍 跨站请求伪造 Django csrf中间件 form表单 ajax c ...

  4. petite-vue源码剖析-逐行解读@vue-reactivity之effect

    当我们通过effect将副函数向响应上下文注册后,副作用函数内访问响应式对象时即会自动收集依赖,并在相应的响应式属性发生变化后,自动触发副作用函数的执行. // ./effect.ts export ...

  5. STL源码剖析读书笔记之vector

    STL源码剖析读书笔记之vector 1.vector概述 vector是一种序列式容器,我的理解是vector就像数组.但是数组有一个很大的问题就是当我们分配 一个一定大小的数组的时候,起初也许我们 ...

  6. 《STL源码剖析》学习之traits编程

    侯捷老师在<STL源码剖析>中说到:了解traits编程技术,就像获得“芝麻开门”的口诀一样,从此得以一窥STL源码的奥秘.如此一说,其重要性就不言而喻了.      之前已经介绍过迭代器 ...

  7. 【Python源码剖析】对象模型概述

    Python 是一门 面向对象 语言,实现了一个完整的面向对象体系,简洁而优雅. 与其他面向对象编程语言相比, Python 有自己独特的一面. 这让很多开发人员在学习 Python 时,多少有些无所 ...

  8. Golang 源码剖析:log 标准库

    Golang 源码剖析:log 标准库 原文地址:Golang 源码剖析:log 标准库 日志 输出 2018/09/28 20:03:08 EDDYCJY Blog... 构成 [日期]<空格 ...

  9. 《python解释器源码剖析》第13章--python虚拟机中的类机制

    13.0 序 这一章我们就来看看python中类是怎么实现的,我们知道C不是一个面向对象语言,而python却是一个面向对象的语言,那么在python的底层,是如何使用C来支持python实现面向对象 ...

随机推荐

  1. SuperMaze(Hello World 团队)Alpha版使用说明

    一.产品介绍 超级迷宫是一款android的手机游戏,目前我们已经在PC 端成功实现大体功能,虽然虽然迷宫游戏不少但我们的游戏渐渐的会假如自己的特色功能,尽量吸引用户,通过游戏开发智力,通过游戏打发无 ...

  2. 冲刺Two之站立会议9

    今天我们团队主要针对软件的功能进行了改进.因为它目前可以实现视频通话,语音聊天,文件传输和文字聊天的通信功能,我们想要在它的基础上实现临时局域群聊和群聊视频的功能,目前还没有完全实现.

  3. Oracle系列(三): 情景查询一 a表中有个fid字段,逗号分隔开来,b表中有id字段及其他信息,如何关联a表的fid和和b表的id字段查询

    现在有两个表,表a中 DOC FID 1 a,b,c 2 a,c,d 表b中 ID KEY a A b B c C d D 怎么联合查询出 DOC FID KEY 1 a,b,c A,B,C 2 a, ...

  4. 《软件工程》小组团队项目-小学生四则运算APP(First Sprint)

    <软件工程>团队项目我们小组选择了小学生四则运算APP,在上学期原有的项目基础上进行更新升级.(自我感觉我们团队上学期的小学生四则运算APP是较为成功且实用的,不过这学期学习到了新的知识, ...

  5. HDOJ2010_水仙花数

    一道水题.一直出现Output Limit Exceeded的原因是在while循环中没有终止条件的时候会自动判断并报错,写的时候忘记加!=EOF结束标识了. HDOJ2010_水仙花数 #inclu ...

  6. shell脚本--数值计算

    原生bash不支持简单的数学运算,即使是最简单的加减乘除 但是,可以使用$[]和expr来实现整数运算 如果要实现小数运算,可以使用bc命令 使用$[]来实现: #!/bin/bash #文件名:te ...

  7. Issue: business key in a call activiti

    https://community.alfresco.com/thread/221280-business-key-in-a-call-activity 这个帖子有一些讨论和回复. https://c ...

  8. [读书笔记]Linux命令行与shell编程读书笔记04 安装软件,编辑器注意事项

    1. debian以及redhat两种主流的linux发行版用的包管理工具 debian的包管理工具是 dpkg 再现安装的是 apt apt的工具主要有 apt-get apt-cache apti ...

  9. [转帖] SQL参数化的优点 CopyFrom https://www.cnblogs.com/-lzb/articles/4840671.html

    梦在远方的小猪 感谢原作者...  后面总结的五点感觉挺好的.. 自己之前的知识点一直没有串起来. 转帖记录一下感谢. sql参数化参数化 说来惭愧,工作差不多4年了,直到前些日子被DBA找上门让我优 ...

  10. Linux中gcc与g++编译器的区别

    一.对于C文件 对于C文件gcc和g++所做的事情确实是一样的,g++在编译C文件时调用了gcc. 二.对于C++文件 1.预处理 预处理gcc与g++做了相同的事. 2.编译 gcc无法自动和c++ ...