Go语言标准库之log和三方库zap

news/2024/7/25 2:46:34/文章来源:https://blog.csdn.net/Ricardo2/article/details/138978064

一、Log

1.1 logger基本使用

Go语言内置的log包实现了简单的日志服务。本包也提供了一个预定义的“标准”logger,可以通过调用函数Print系列(Print|Printf|Println)、Fatal系列(Fatal|Fatalf|Fatalln)、和Panic系列(Panic|Panicf|Panicln)来使用,比自行创建一个logger对象更容易使用。

Fatal系列用于输出一条致命错误信息,并调用 os.Exit(1) 终止程序运行。这个函数会在打印完错误信息之后立即调用 os.Exit 退出程序。

package mainimport ("log"
)func main() {log.Println("卡卡西的日志")x := "鸣人"log.Printf("%s的日志\n", x)log.Fatalln("会触发fatal的日志")// 这里的代码不会被执行,因为程序已经在 log.Fatalln 后退出了log.Panicln("会触发panic的日志")
}
2024/05/16 23:03:24 卡卡西的日志
2024/05/16 23:03:24 鸣人的日志       
2024/05/16 23:03:24 会触发fatal的日志
exit status 1 

1.2 logger配置

基础的logger只提供日志的时间信息,如果需要获取更多的信息或者输出方式,可以通过进一步配置实现。

1.2.1 标准配置 log.SetFlags.SetFlags

  • log.SetFlags() 函数用于设置日志的输出格式
  • log.Flags() 函数来获取当前日志包的配置标志,并将其打印输出。

以下是 log 包中定义的一些常用选项:

  • log.Ldate:日期:2009/01/23
  • log.Ltime:时间:01:23:23
  • log.Lmicroseconds:微秒级时间:01:23:23.123123
  • log.Llongfile:文件名和行号:/a/b/c/d.go:23
  • log.Lshortfile:文件名和行号:d.go:23
  • log.LUTC:使用 UTC 时间

下面在记录日志之前先设置一下logger的输出选项:

package mainimport ("fmt""log"
)func main() {log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)log.Println("卡卡西的日志")flags := log.Flags()fmt.Println("Flags:", flags)
}

输出结果

2024/05/16 23:25:39.519510 d:/go/练习/main.go:10: 卡卡西的日志
Flags: 13

1.2.2 前缀配置 log.SetPrefix 和 log.Prefix

  • log.SetPrefix 用于设置日志输出的前缀,它接受一个字符串作为参数,这个字符串将会作为日志信息的前缀显示在每条日志的最前面。
  • log.Prefix 则是一个属性,用于获取当前日志输出的前缀。
func main() {log.SetPrefix("复制忍者:")log.Println("卡卡西")prefix := log.Prefix()fmt.Println(prefix)
}

输出结果是

复制忍者:2024/05/18 10:06:56 卡卡西
复制忍者:  

1.2.3 输出位置 log.SetOutput

log.SetOutput 用于设置日志输出的目标。这个函数接受一个 io.Writer 类型的参数,将日志输出到指定的 io.Writer 实现。

func main() {file, err := os.OpenFile("logfile.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)if err != nil {log.Fatal("Failed to open logfile.log", err)}defer file.Close()log.SetOutput(file)log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)log.Println("卡卡西")log.SetPrefix("复制忍者:")
}

1.3 logger对象创建

log.New 用于创建一个新的 Logger 对象。这个函数接受三个参数:一个实现了 io.Writer 接口的目标输出流、一个用于添加前缀的字符串、以及一个用于指定日志属性的标志选项。

func main() {// 创建一个新的 Logger 对象,将日志输出到标准错误输出,并添加前缀 "ERROR: "logger := log.New(os.Stderr, "ERROR: ", log.LstdFlags)// 使用新创建的 Logger 对象输出日志logger.Println("This is an error message.")
}

输出结果

ERROR: 2024/05/18 10:27:40 This is an error message.

二、第三方日志库 Zap

注意:Zap并不是Go的标准库,而是为了解决Go内置log库功能有限的问题,所引入的第三方日志库。在此处介绍Zap是为了方便与log库进行对比学习。

  • Zap的优点:快、结构化,分日志级别。

Zap 日志库提供了两种类型的日志记录器:Sugared LoggerLogger。它们分别适用于不同的日志记录场景。

  1. Sugared Logger:

    • Sugared Logger 提供了结构化和格式化的日志记录功能,支持使用类似 Printf 风格的方法记录日志。
    • 它使用了结构化的上下文字段,可以方便地记录关键-值对形式的日志信息。
    • Sugared Logger 适合用于一般的日志记录需求,提供了更直观、易用的 API。
  2. Logger:

    • Logger 提供了更底层的、零分配(zero-allocation)的日志记录功能,适用于高性能、高吞吐量的日志记录需求。
    • 它的 API 相对更加简洁,不支持结构化的上下文字段,但在性能方面有优势。
    • Logger 适合用于需要尽量减少内存分配和提升性能的场景。

根据具体的需求和场景,可以选择使用 Zap 提供的 Sugared Logger 或 Logger 来实现相应的日志记录功能。

2.1 Logger

  1. 调用zap.NewProduction()或者zap.NewDevelopment()或者zap.Example()创建了一个 Zap 日志记录器
  2. 通过Logger调用Info/Error等
  3. 程序结束前调用 logger.Sync() 来确保所有日志都被输出

zap.NewProduction():会配置 Logger 以适应生产环境的需求,例如默认会将日志输出到标准错误输出,并且会禁用堆栈跟踪等详细的调试信息,以减少对性能的影响。适合用于生产环境中记录稳定运行日志的场景。
zap.NewDevelopment():会配置 Logger 以便于开发过程中更好地跟踪和调试日志,例如会输出更详细的调试信息和堆栈跟踪。适合用于开发和调试过程中辅助定位问题、跟踪日志的场景。
zap.Example():是一个示例方法,提供了一个简单的例子,演示了如何创建 Logger 实例、记录不同级别的日志、以及如何添加结构化的上下文字段等操作。

下面通过简单的http get介绍logger的用法

package mainimport ("net/http""go.uber.org/zap"
)var logger *zap.Logger// 初始化日志记录器
func InitLogger() {logger, _ = zap.NewProduction()
}// 发送 HTTP GET 请求
func httpGet(url string){resp, err := http.Get(url)if err != nil {// 如果请求中出现错误,记录错误日志logger.Error("Error fetching url: ",zap.String("url",url),zap.Error(err))} else {// 如果请求成功,记录成功日志logger.Info("Success: ",zap.String("statusCode", resp.Status),zap.String("url", url))resp.Body.Close()}
}func main() {InitLogger()// 使用了 defer 关键字来延迟执行 logger.Sync(),以确保在程序退出前执行同步操作defer logger.Sync()httpGet("https://blog.csdn.net/Ricardo2/article/details/134253323")httpGet("www.google.com")
}

输出结果

{"level":"info","ts":1716001211.442937,"caller":"练习/main.go:26","msg":"Success: ","statusCode":"403 Forbidden","url":"https://blog.csdn.net/Ricardo2/article/details/134253323"}
{"level":"error","ts":1716001211.443463,"caller":"练习/main.go:20","msg":"Error fetching url: ","url":"www.google.com","error":"Get \"www.google.com\": unsupported protocol scheme \"\"","stacktrace":"main.httpGet\n\td:/go/练习/main.go:20\nmain.main\n\td:/go/练习/main.go:41\nruntime.main\n\tE:/goland/src/runtime/proc.go:250"}

2.2 SugaredLogger

大部分的实现基本都相同。
惟一的区别是,我们通过logger.Sugar()方法来获取一个SugaredLogger。

package mainimport ("net/http""go.uber.org/zap"
)var sugarLogger *zap.SugaredLogger// 初始化日志记录器
func InitLogger() {logger, _ := zap.NewProduction()sugarLogger = logger.Sugar()
}// 发送 HTTP GET 请求
func httpGet(url string){sugarLogger.Debugf("Trying to grt request for %s", url)resp, err := http.Get(url)if err != nil {// 如果请求中出现错误,记录错误日志sugarLogger.Error("Error fetching url: ",zap.String("url",url),zap.Error(err))} else {// 如果请求成功,记录成功日志sugarLogger.Info("Success: ",zap.String("statusCode", resp.Status),zap.String("url", url))resp.Body.Close()}
}func main() {InitLogger()// 使用了 defer 关键字来延迟执行 logger.Sync(),以确保在程序退出前执行同步操作defer sugarLogger.Sync()httpGet("https://blog.csdn.net/Ricardo2/article/details/134253323")httpGet("www.google.com")
}

输出结果

{"level":"info","ts":1716001865.691326,"caller":"练习/main.go:28","msg":"Success: {statusCode 15 0 403 Forbidden <nil>} {url 15 0 https://blog.csdn.net/Ricardo2/article/details/134253323 <nil>}"}
{"level":"error","ts":1716001865.6924412,"caller":"练习/main.go:22","msg":"Error fetching url: {url 15 0 www.google.com <nil>} {error 26 0  Get \"www.google.com\": unsupported protocol scheme \"\"}","stacktrace":"main.httpGet\n\td:/go/练习/main.go:22\nmain.main\n\td:/go/练习/main.go:43\nruntime.main\n\tE:/goland/src/runtime/proc.go:250"}

2.3 日志级别

Zap 日志库支持以下几种日志级别,可以根据不同的需求来选择合适的级别记录日志:

  • Debug:
    用于记录调试过程中的详细信息,通常在开发和调试阶段使用。
    使用 logger.Debug() 方法记录 Debug 级别的日志。
  • Info:
    用于记录程序运行过程中的一般信息,例如启动信息、关键事件等。
    使用 logger.Info() 方法记录 Info 级别的日志。
  • Warn:
    用于记录可能出现问题但不会影响程序正常运行的警告信息,例如参数使用不当、潜在的问题等。
    使用 logger.Warn() 方法记录 Warn 级别的日志。
  • Error:
    用于记录程序中的错误信息,例如异常、错误状态等。
    使用 logger.Error() 方法记录 Error 级别的日志。
  • DPanic:
    用于记录严重的错误,会导致程序进入恐慌状态的错误。
    使用 logger.DPanic() 方法记录 DPanic 级别的日志。
  • Panic:
    用于记录导致程序无法继续正常运行的错误,记录后会触发程序 panic。
    使用 logger.Panic() 方法记录 Panic 级别的日志。
  • Fatal:
    用于记录导致程序无法继续运行的严重错误,记录后会触发程序退出。
    使用 logger.Fatal() 方法记录 Fatal 级别的日志。

2.4 Zap配置

2.4.1 标准配置

下面介绍如何对Zap的日志做详细的配置:

  • 如何写入日志
  • 日志写入到哪里
  • 写入什么级别的日志

具体来说,将使用zap.New(…)方法来手动传递所有配置

func New(core zapcore.Core, options ...Option) *Logger

zapcore.Core 接口类型的实例,定义了日志记录的核心功能,包括日志级别的判断LogLevel、格式化日志消息Encoder、输出日志的目的地WriteSyncer等。

(1)Encoder:编码器(如何写入日志)。我们将使用开箱即用的NewJSONEncoder(),并使用预先设置的ProductionEncoderConfig()

zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())

(2)WriterSyncer :指定日志将写到哪里去。我们使用zapcore.AddSync()函数并且将打开的文件句柄传进去。通过使用 AddSync 函数,可以将一个标准的 Go io.Writer 实例(比如文件、标准输出等)包装成一个符合 Zap 日志库要求的 WriteSyncer 实例

file, _ := os.Create("./test.log")
writeSyncer := zapcore.AddSync(file)

(3)Log Level:哪种级别的日志将被写入。

下面将修改上述部分中的Logger代码,主要是重写InitLogger()方法。

package mainimport ("net/http""os""go.uber.org/zap""go.uber.org/zap/zapcore"
)var sugarLogger *zap.SugaredLoggerfunc getEncoder() zapcore.Encoder {return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}func getLogWriter() zapcore.WriteSyncer {file, _ := os.Create("./test.log")return zapcore.AddSync(file)
}// 初始化日志记录器
func InitLogger() {writeSyncer := getLogWriter()encoder := getEncoder()core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)logger := zap.New(core)sugarLogger = logger.Sugar()
}// 发送 HTTP GET 请求
func httpGet(url string) {sugarLogger.Debugf("Trying to grt request for %s", url)resp, err := http.Get(url)if err != nil {// 如果请求中出现错误,记录错误日志sugarLogger.Error("Error fetching url: ",zap.String("url", url),zap.Error(err))} else {// 如果请求成功,记录成功日志sugarLogger.Info("Success: ",zap.String("statusCode", resp.Status),zap.String("url", url))resp.Body.Close()}
}func main() {InitLogger()// 使用了 defer 关键字来延迟执行 logger.Sync(),以确保在程序退出前执行同步操作defer sugarLogger.Sync()httpGet("https://blog.csdn.net/Ricardo2/article/details/134253323")httpGet("www.google.com")
}

结果是

{"level":"debug","ts":1716607412.904807,"msg":"Trying to grt request for https://blog.csdn.net/Ricardo2/article/details/134253323"}
{"level":"info","ts":1716607415.0869148,"msg":"Success: {statusCode 15 0 200 OK <nil>} {url 15 0 https://blog.csdn.net/Ricardo2/article/details/134253323 <nil>}"}
{"level":"debug","ts":1716607415.0869148,"msg":"Trying to grt request for www.google.com"}
{"level":"error","ts":1716607415.0869148,"msg":"Error fetching url: {url 15 0 www.google.com <nil>} {error 26 0  Get \"www.google.com\": unsupported protocol scheme \"\"}"}

2.4.2 修改输出格式

NewJSONEncoder() 创建的编码器将日志事件格式化为 JSON 格式的字符串。这种格式在日志收集系统、日志分析工具等场景中通常更易于处理和解析。
NewConsoleEncoder() 创建的编码器将日志事件格式化为人类可读的文本格式,通常采用一种类似于控制台输出的格式。这种格式适合在终端中查看日志。

func getEncoder() zapcore.Encoder {return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
}

结果是

1.7166081613816104e+09	debug	Trying to grt request for https://blog.csdn.net/Ricardo2/article/details/134253323
1.7166081620810077e+09	info	Success: {statusCode 15 0 200 OK <nil>} {url 15 0 https://blog.csdn.net/Ricardo2/article/details/134253323 <nil>}
1.7166081620810077e+09	debug	Trying to grt request for www.google.com
1.7166081620815365e+09	error	Error fetching url: {url 15 0 www.google.com <nil>} {error 26 0  Get "www.google.com": unsupported protocol scheme ""}

2.4.3 修改时间展示方式

首先要覆盖Encoder终默认的ProductionConfig(),进行手动配置:

  • 修改时间编码器
  • 在日志文件中使用大写字母记录日志级别
func getEncoder() zapcore.Encoder {encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoderencoderConfig.EncodeLevel = zapcore.CapitalLevelEncoderreturn zapcore.NewConsoleEncoder(encoderConfig)
}

2.4.4 增加调用者函数的信息

将在zap.New(..)函数中添加一个Option。

logger := zap.New(core, zap.AddCaller())

最后,结果是

2024-05-25T11:41:42.283+0800	DEBUG	练习/main.go:37	Trying to grt request for https://blog.csdn.net/Ricardo2/article/details/134253323
2024-05-25T11:41:43.196+0800	INFO	练习/main.go:47	Success: {statusCode 15 0 200 OK <nil>} {url 15 0 https://blog.csdn.net/Ricardo2/article/details/134253323 <nil>}
2024-05-25T11:41:43.199+0800	DEBUG	练习/main.go:37	Trying to grt request for www.google.com
2024-05-25T11:41:43.199+0800	ERROR	练习/main.go:41	Error fetching url: {url 15 0 www.google.com <nil>} {error 26 0  Get "www.google.com": unsupported protocol scheme ""}

2.4.5 将日志同时输出到文件和终端

func getLogWriter() zapcore.WriteSyncer {file, _ := os.Create("./test.log")// 利用io.MultiWriter支持文件和终端两个输出目标ws := io.MultiWriter(file, os.Stdout)return zapcore.AddSync(ws)
}

2.4.6 将err日志单独输出到文件

将ERROR级别的日志单独输出到一个名为xx.err.log的日志文件中。

func InitLogger() {encoder := getEncoder()// test.log记录全量日志logF, _ := os.Create("./test.log")c1 := zapcore.NewCore(encoder, zapcore.AddSync(logF), zapcore.DebugLevel)// test.err.log记录ERROR级别的日志errF, _ := os.Create("./test.err.log")c2 := zapcore.NewCore(encoder, zapcore.AddSync(errF), zap.ErrorLevel)// 使用NewTee将c1和c2合并到corecore := zapcore.NewTee(c1, c2)logger = zap.New(core, zap.AddCaller())
}

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

线性回归计算举例

使用正规方程计算&#xff08;一元线性回归&#xff09; import numpy as np import matplotlib.pyplot as plt # 转化成矩阵 X np.linspace(0, 10, num 30).reshape(-1, 1) # 斜率和截距&#xff0c;随机生成 w np.random.randint(1, 5, size 1) b np.random.randint(1,…

骆驼大赛

目录 一&#xff0c;主版图 二&#xff0c;骰子 三&#xff0c;初始设置 四&#xff0c;核心规则 五&#xff0c;结算 这是适合5-8人玩的一个概率推理类的回合制桌游。 一&#xff0c;主版图 赛道由16个格子组成&#xff0c;编号为1-16。 一共7个骆驼&#xff0c;其中正…

基于 Java 的浏览器——JxBrowser使用分享

软件介绍 JxBrowser 是一个基于 Java 的浏览器&#xff0c;它使用 Chromium 引擎来提供高性能的网页渲染和丰富的功能。它支持多种 GUI 框架&#xff0c;如 Swing、JavaFX 和 SWT&#xff0c;使得在 Java 应用程序中嵌入浏览器组件变得简单。 JxBrowser 是一个适用于多种用途…

MySQL索引和视图

MySQL索引和视图是关系型数据库MySQL中的两个重要概念。索引用于优化数据库的查询性能&#xff0c;而视图用于提供一个逻辑上的表结构&#xff0c;方便用户查询和操作数据。 索引是一种数据结构&#xff0c;可以加速对数据库表中的数据进行查询的速度。通过创建索引&#xff0…

基于QEMU-aarch64学习UEFI(EDK2)-7Print打印函数

1 基于QEMU-aarch64学习UEFI(EDK2)-7Print打印函数 文章目录 1 基于QEMU-aarch64学习UEFI(EDK2)-7Print打印函数1.1 Print打印函数输出字符串1.2 Print打印函数其他用法程序开发我们以 edk2-stable202302版本为准。 1.1 Print打印函数输出字符串 我们把edk2/MdeModulePkg/App…

Py列表(list)

目录 正向索引&#xff1a; 反向索引&#xff1a; 嵌套列表&#xff1a; 修改列表中的值 列表常用的方法 实例 练习&#xff1a; 正向索引&#xff1a; 从0开始&#xff0c;依次递增。第一个元素的索引为0&#xff0c;第二个元素的索引为1&#xff0c;依此类推。 列表的下标…

Windows 使用技巧

Windows 使用技巧 ①局域网内共享文件 ②CTRL Y 和 CTRL Z ①局域网内共享文件 第一步&#xff1a; 选择要共享的文件&#xff08;分享方操作&#xff09; 第二步&#xff1a; 右键打开属性&#xff0c;选择共享&#xff08;分享方操作&#xff09; 第三步&#xff1a; …

redis数据类型set,zset

华子目录 Set结构图相关命令sdiff key1 [key2]sdiffstore destination key1 [key2...]sinter key1 [key2...]sinterstore destination key1 [key2...]sunion key1 [key2...]sunionstore destination key1 [key2...]smove source destination memberspop key [count]sscan key c…

使用Word表格数据快速创建图表

实例需求&#xff1a;Word的表格如下所示&#xff0c;标题行有合并单元格。 现在需要根据上述表格数据&#xff0c;在Word中创建如下柱图。如果数据在Excel之中&#xff0c;那么创建这个图并不复杂&#xff0c;但是Word中就没用那么简单了&#xff0c;虽然Word中可以插入图表&a…

[IMX6ULL驱动开发]-Linux对中断的处理(二)

上一篇文章中&#xff0c;引入了Linux对于中断的一些简略流程以及中断抽象为具体实际形象。此文章主要是继续加深对Linux对中断的处理流程以及一些相应的数据结构。 目录 Linux对中断的扩展&#xff1a;硬件中断、软件中断 多中断处理 中断上下部处理流程 发生中断A&#…

Golang | Leetcode Golang题解之第104题二叉树的最大深度

题目&#xff1a; 题解&#xff1a; func maxDepth(root *TreeNode) int {if root nil {return 0}queue : []*TreeNode{}queue append(queue, root)ans : 0for len(queue) > 0 {sz : len(queue)for sz > 0 {node : queue[0]queue queue[1:]if node.Left ! nil {queue…

第十四届蓝桥杯c++研究生组

A 关键思路是求每个十进制数的数字以及怎么在一个数组中让判断所有的数字次数相等。 求每个十进制的数字 while(n!0){int x n%10;//x获取了n的每一个位数字n/10;}扩展&#xff1a;求二进制的每位数字 &#xff08;注意&#xff1a;进制转换、1的个数、位运算&#xff09; x…

骨折分类数据集1129张10类别

数据集类型&#xff1a;图像分类用&#xff0c;不可用于目标检测无标注文件 数据集格式&#xff1a;仅仅包含jpg图片&#xff0c;每个类别文件夹下面存放着对应图片 图片数量(jpg文件个数)&#xff1a;1129 分类类别数&#xff1a;10 类别名称:["avulsion_fracture",…

景源畅信:抖音小店新手小白如何做好运营?

在数字时代的浪潮中&#xff0c;抖音小店成为了众多创业者和商家的新宠。但面对激烈的市场竞争和不断变化的平台规则&#xff0c;新手小白如何才能在抖音小店的海洋里稳健航行&#xff0c;捕捉到属于自己的商机呢?接下来的内容将为你揭晓答案。 一、精准定位&#xff0c;明确目…

常见开源蜜罐系统

蜜罐系统&#xff08;Honeypot&#xff09;在信息安全领域中是一种被广泛使用的技术&#xff0c;旨在吸引和诱导黑客入侵&#xff0c;从而获取和分析攻击者的行为和手段。以下是一些常见的蜜罐系统的介绍&#xff1a; HFish开源蜜罐系统 特点&#xff1a; 多功能&#xff1a;支…

Windows hook介绍与代码演示

Windows Hook 是一种机制&#xff0c;允许应用程序监视系统或处理特定事件。它可以拦截和更改消息&#xff0c;甚至可以插入到其他应用程序的消息处理机制中。Windows 提供了多种挂钩类型&#xff0c;例如键盘挂钩、鼠标挂钩、消息挂钩等。 hook代码实现 下面是一个使用 Wind…

就说说Java初学者求职准备项目的正确方式

当下不少Java初学者也知道求职时项目的重要程度&#xff0c;但在简历上写项目和准备面试项目时&#xff0c;真有可能走弯路&#xff0c;这样的话&#xff0c;加重学习负担还是小事&#xff0c;还真有可能导致无法入职。 1 对于在校生和应届生来说&#xff0c;你去跑通个学习项…

pillow学习3

Pillow库中&#xff0c;图像的模式代表了图像的颜色空间。以下是一些常见的图像模式及其含义&#xff1a; L&#xff08;灰度图&#xff09;&#xff1a;L模式表示图像是灰度图像&#xff0c;每个像素用8位表示&#xff08;范围为0-255&#xff09;&#xff0c;0表示黑色&#…

idea的project structure下project [lauguage ]()level 没有java的sdk17选项如何导入

idea的project structure下project lauguage level 没有java的sdk17选项如何导入 别导入了&#xff0c;需要升级idea版本。idea中没有project language level没有17如何添加 - CSDN文库 别听这文章瞎扯淡 2021版本就是没有&#xff0c;直接卸载升级到最新版本就可以了。没办法…

qmt量化交易策略小白学习笔记第8期【qmt编程之获取股票资金流向数据--内置Python】

qmt编程之获取股票资金流向数据 qmt更加详细的教程方法&#xff0c;会持续慢慢梳理。 也可找寻博主的历史文章&#xff0c;搜索关键词查看解决方案 &#xff01; 感谢关注&#xff0c;需免费开通量化回测与咨询实盘权限&#xff0c;可以和博主联系&#xff01; 获取股票资金…