最近百家饭OpenAPI平台的JS API调用代码自动生成功能顺利进展中,进展情况可以关注我们的博客,我们计划先在内部完成“自举”(自己平台开发的功能支持自己的开发……),将百家饭平台自身的前后台交互部分迁移到自己开发的JS代码生成模式上来。

整个过程比较长,分成了golang部分和JavaScript部分,所以我们分篇来介绍,把其中有用的技术点也拎出来说

  1. 第一篇:Golang生成OpenAPI接口文档(Swag工具试用)
  2. 第二篇:Golang OpenAPI工具Swag修正-logrus篇
  3. 第三篇:Golang OpenAPI工具Swag修正-go ast篇
  4. 第四篇:也谈Javascript里的commonjs模块和es6模块

参与Swag开源

上一篇说到我们用swag工具的过程中总体还是比较顺利,但是遇到了一些小问题,这一篇我们介绍一下我们从这些问题出发,参与解决这些问题的过程,因为swag命令本身输出内容非常少,所以能得到的帮助信息有限,好在这个程序是开源在github上的开源程序,我们fork了下来,花了两天尝试解决了相关问题:

增加更多的信息输出级别

对于一个cli程序来讲,丰富的过程信息输出,对于确定过程中的问题,是非常必要的,我们在解决使用问题的过程中,首先也需要了解具体出错的原因,才能进行调整。看过swag的代码后,我们发现他用了默认的系统log库作为标准日志输出,另配置了一个debuger构建了两层日志体系,但是标准设置下就是已经覆盖debug级别的状态,对于我们来讲,还是信息量太少。

var logger swag.Debugger //nil时使用默认值
if ctx.Bool(quietFlag) {
  logger = log.New(ioutil.Discard, "", log.LstdFlags) //quiet设置时,将debuger指向Discard
}

所有首先一个问题是要将两层日志级别扩展成为多层,有两个工作要做,一个是在入参中增加设置日志级别的参数。我们选择在原库的基础上增加一个verbose参数,设置打开后,显示更多日志信息:

	&cli.BoolFlag{
		Name:    verboseFlag,
		Aliases: []string{"v"},
		Value:   false,
		Usage:   "Show more info of executation",
	},

第二个问题就是如何扩展多层日志框架

这个问题其实直接就是建议使用日志库了,我们提交了commit后,有个外国友人也评论如下:

As a consumer of this library, I'm generally leery of libraries tying themselves to any particular logging library. While most of my applications do depend on Logrus, I'd hate for an application which uses something else – e.g., the standard logger, zap, or Zerolog – to be saddled with a transitive dependency on Logrus.

At the same time, I understand the desire to avail oneself of more structured logging in the swag CLI application. Perhaps the swag CLI could be moved into its own module so that it can depend on Logrus independently of the top-level library?

基本就是说他大部分都是用的logrus,遇到其他库的都不是很爽,也不建议软件自己再做一遍日志库。

确实也是如此,logrus的使用确实是比较广泛的,所以我们也打算利用logrus的Info和Debug层来兼容目前的两级日志,同时利用Trace层来扩展更详细的信息, 我们在自己的fork里面,先把Debuger这个interface定义扩展成为了兼容logrus的logger结构

type Logger interface{
        WithField(key string, value interface{}) *logrus.Entry
        WithFields(fields logrus.Fields) *logrus.Entry
        WithError(err error) *logrus.Entry
        
        Tracef(format string, args ...interface{})
        Debugf(format string, args ...interface{})
        Infof(format string, args ...interface{})
        Printf(format string, args ...interface{})
        Warnf(format string, args ...interface{})
        Warningf(format string, args ...interface{})
        Errorf(format string, args ...interface{})
        Fatalf(format string, args ...interface{})
        Panicf(format string, args ...interface{})

        Trace(args ...interface{})
        Debug(args ...interface{})
        Info(args ...interface{})
        Print(args ...interface{})
        Warn(args ...interface{})
        Warning(args ...interface{})
        Error(args ...interface{})
        Fatal(args ...interface{})
        Panic(args ...interface{})

        Traceln(args ...interface{})
        Debugln(args ...interface{})
        Infoln(args ...interface{})
        Println(args ...interface{})
        Warnln(args ...interface{})
        Warningln(args ...interface{})
        Errorln(args ...interface{})
        Fatalln(args ...interface{})
        Panicln(args ...interface{})

        GetLevel() logrus.Level
}

这样就可以直接把logrus.New的对象作为logger使用,这个定义基本是拷贝了logrus.FieldLogger的定义,除了增加了GetLevel() logrus.Level作为获取级别的函数,按道理其实这种情况下直接定义成下面这样可能就可以了,但是按道理为了隔离和减少依赖,拷贝一份接口定义是合理的,但是我们没做到的是GetLevel函数还保留了logrus的依赖,这块可能下一步还需要修正一下。

// Debugger is the interface that wraps the basic Printf method.
type Logger interface{
        logrus.FieldLogger
        GetLevel() logrus.Level
}

修改logrus的默认日志输出样式

改成logrus之后,我们还需要把logrus默认的日志样式修改一下,默认的logrus和swag当前的样式不一样,既然是改别人的代码,原则上还是希望只做增加,不做修改,本着这个原则,我们增加了一个logrus的formatter。

logrus要修改样式,是利用formatter来做的,方式如下

func NewLogger(ws ...io.Writer) *logrus.Logger {
	var logger = logrus.New()
    //New完一个logger之后,通过SetFormatter函数为他指定一个Formatter
	logger.SetFormatter(&logFormatter{})
	if len(ws) > 0 {
		logger.SetOutput(ws[0])
	}
	return logger
}

Formater是一个自定义类,只要实现以下接口即可

type Formatter interface {
	Format(*Entry) ([]byte, error)
}

为了和原来的模式相同,我们自定义formatter如下

type logFormatter struct {
}

// Format building log message.
func (f *logFormatter) Format(entry *logrus.Entry) ([]byte, error) {
    //defaultLogFormat = "%time% %lvl%%msg%",在别处定义
	var output = defaultLogFormat

	output = strings.Replace(output, "%time%", entry.Time.Format(defaultTimestampFormat), 1)

	output = strings.Replace(output, "%msg%", entry.Message, 1)

	var levelStr = ""
	if entry.Level > logrus.DebugLevel {
		levelStr = "[" + strings.ToUpper(entry.Level.String()) + "]"
	}
	output = strings.Replace(output, "%lvl%", levelStr, 1)

	for k, val := range entry.Data {
		output += fmt.Sprintf("\t%s=%v", k, val)
	}

	return []byte(output + "\n"), nil
}

总的功能就是这样,基本就是把传进来的logrus.Entry进行解析,处理成byte数组,我们的基本思路是定义了一个带占位符的格式串(代码中的defaultLogFormat),然后依照格式串的内容,将其中的占位符替换成为logrus.Entry(也就是一条日志信息)中的具体内容,最后拼接上所有的field内容即可。

其中为了兼容原来的日志格式,对Debug以下的内容不打印级别信息,对trace打印级别信息。最后效果如下:

2022/07/08 09:43:49 Using overrides from .swaggo
2022/07/08 09:43:49 Generate swagger docs....
2022/07/08 09:43:49 Generate general API Info, search dir:./
2022/07/08 09:43:55 [TRACE]response found @success
2022/07/08 09:43:55 [TRACE]parse array schema of model.Source in ctrl
2022/07/08 09:43:55 [TRACE]find model.Source in imports
2022/07/08 09:43:55 [TRACE]model is not alias pkg name
2022/07/08 09:43:55 Generating model.Source
2022/07/08 09:43:55 [TRACE]response found @failure
2022/07/08 09:43:55 [TRACE]parse object schema of errors.Error in ctrl
2022/07/08 09:43:55 [TRACE]find errors.Error in imports
2022/07/08 09:43:55 [TRACE]errors is not alias pkg name
2022/07/08 09:43:56 [TRACE]found in imports codeup.aliyun.com/njzhenwo/baijiafan/base/errors
2022/07/08 09:43:56 Generating errors.Error
2022/07/08 09:43:56 create docs.go at  swag/docs.go
2022/07/08 09:43:56 create swagger.json at  swag/swagger.json
2022/07/08 09:43:56 create swagger.yaml at  swag/swagger.yaml

其中[Trace]开头的就是我们追加的详细日志层。

到这,基本我们的日志修改部分就结束了,当然为了让日志能在软件执行的多个层面里打出来,我们还在仓库中增加了很多logger相关的传参,这些就不细讲了,我们fork的库在这里,修改已经push到了原仓库,这是commit的链接,还在等待review。这个库我们陆续还会进行一些bug的修正。他的授权协议是mit,所以我们应该也可以在后期把他放到我们的百家饭OpenAPI工具安装包中。但是我觉得至少还需要增加更多的日志打印,最好完成国际化之后再来做这个事情。

下一篇我们来讲swag对go ast的应用。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐