一个躺平程序员的回光返照,内容大部分是有道翻译小部分是百度内容,如果有错误请在评论区提出,马上改
填昨天留的坑
LoggerWithConfig
// LoggerWithConfig instance a Logger middleware with config.
// LoggerWithConfig实例是一个带有config的Logger中间件。
/**
// LoggerConfig defines the config for Logger middleware.
// LoggerConfig定义了Logger中间件的配置。
type LoggerConfig struct {
// Optional. Default value is gin.defaultLogFormatter
Formatter LogFormatter
// Output is a writer where logs are written.
// Optional. Default value is gin.DefaultWriter.
// Output是写入日志的写入器。
//可选的。默认值为“gin.DefaultWriter”。
Output io.Writer
// SkipPaths is a url path array which logs are not written.
// skippath是一个不写入日志的url路径数组。
// Optional.
SkipPaths []string
}
*/
func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
// formatter 赋默认值
formatter := conf.Formatter
if formatter == nil {
formatter = defaultLogFormatter
}
// out 赋默认值
out := conf.Output
if out == nil {
out = DefaultWriter
}
notlogged := conf.SkipPaths
isTerm := true
/**
out.()没有看懂,断点看返回值是 w: *os.File | 0xc0000d8008 ok: true, 应该是文件读取之类的
w.Fd()返回引用打开文件的Windows句柄。
文件读取失败或者配置中TERM == dumb 或者 文件描述符不是terminal|cygwin|msys2, isTerm为false
*/
if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" ||
(!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) {
isTerm = false
}
var skip map[string]struct{}
// 判断不写入日志的url路径,如果有数据则生成对应条数的map切片并赋初值(key)
if length := len(notlogged); length > 0 {
skip = make(map[string]struct{}, length)
for _, path := range notlogged {
skip[path] = struct{}{}
}
}
// 返回一个上下文处理方法
return func(c *Context) {
// Start timer
start := time.Now()
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
// Process request
// 继续执行,日志为后置中间件(钩子)
c.Next()
// 代码执行完后 继续日志处理
// Log only when path is not being skipped
// 判断数据是否在不记录日志map中,如果不在则进入
if _, ok := skip[path]; !ok {
// 日志初始化
param := LogFormatterParams{
Request: c.Request,
// isTerm显示gin的输出描述符是否指向终端。
isTerm: isTerm,
Keys: c.Keys,
}
// Stop timer
// 完成时间,完善日志参数
param.TimeStamp = time.Now()
param.Latency = param.TimeStamp.Sub(start)
param.ClientIP = c.ClientIP()
param.Method = c.Request.Method
param.StatusCode = c.Writer.Status()
param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()
param.BodySize = c.Writer.Size()
// 判断请求参数是否为空,如果不为空则拼接处理path
if raw != "" {
path = path + "?" + raw
}
param.Path = path
//对其操作数使用默认格式的Fprint格式,并写入w。
//当两个操作数都不是字符串时,在操作数之间添加空格。
//返回写入的字节数和遇到的任何写入错误。
fmt.Fprint(out, formatter(param))
}
}
}
RecoveryWithWriter
func RecoveryWithWriter(out io.Writer, recovery ...RecoveryFunc) HandlerFunc {
if len(recovery) > 0 {
return CustomRecoveryWithWriter(out, recovery[0])
}
return CustomRecoveryWithWriter(out, defaultHandleRecovery)
}
CustomRecoveryWithWriter
// CustomRecoveryWithWriter returns a middleware for a given writer that recovers from any panics and calls the provided handle func to handle it.
// CustomRecoveryWithWriter为给定的写入器返回一个中间件,该中间件可以从任何panics 中恢复,并调用提供的处理程序来处理它
func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
// 定义日志
var logger *log.Logger
if out != nil {
// 判断out不为空初始化logger
logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
}
// 返回匿名方法
return func(c *Context) {
// 定义延迟执行的匿名方法并且立即调用
// 类似于final在最后执行
defer func() {
// 给err赋值并判断是否为空
if err := recover(); err != nil {
// Check for a broken connection, as it is not really a
// condition that warrants a panic stack trace.
//检查断开的连接,因为这并不是保证panic堆栈跟踪的真正条件。
var brokenPipe bool
if ne, ok := err.(*net.OpError); ok {
if se, ok := ne.Err.(*os.SyscallError); ok {
// 判断se的错误信息中是否报刊broken pipe 或者包含connection reset by peer 如果包含其中一个则brokenPipe为true(默认值是false)
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
brokenPipe = true
}
}
}
// 判断logger不为空
if logger != nil {
// Stack返回一个格式良好的堆栈帧,跳过skip frames
stack := stack(3)
// 返回http请求数据,包含header,body
httpRequest, _ := httputil.DumpRequest(c.Request, false)
// 切割
headers := strings.Split(string(httpRequest), "\r\n")
for idx, header := range headers {
current := strings.Split(header, ":")
// 判断header中有Authorization则给对应header重新赋值
if current[0] == "Authorization" {
headers[idx] = current[0] + ": *"
}
}
// 将header转成字符串格式
headersToStr := strings.Join(headers, "\r\n")
if brokenPipe {
logger.Printf("%s\n%s%s", err, headersToStr, reset)
} else if IsDebugging() {
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
timeFormat(time.Now()), headersToStr, err, stack, reset)
} else {
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
timeFormat(time.Now()), err, stack, reset)
}
}
if brokenPipe {
// If the connection is dead, we can't write a status to it.
// 如果连接已死,我们就不能向它写入状态。
c.Error(err.(error)) // nolint: errcheck
//中止阻止挂起的处理程序被调用。注意,这不会停止当前的处理程序。
//假设你有一个授权中间件来验证当前请求是否被授权。
//如果授权失败(例如:密码不匹配),调用Abort以确保剩余的处理程序
c.Abort()
} else {
// 调用RecoveryFunc方法
handle(c, err)
}
}
}()
// 继续执行
c.Next()
}
}