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

一、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.mzph.cn/bicheng/15255.shtml

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

相关文章

C++ 数据结构算法 学习笔记(32) -五大排序算法

C 数据结构算法 学习笔记(32) -五大排序算法 选择算法 如下若有多个女生的身高需要做排序: 常规思维: 第一步先找出所有候选美女中身高最高的&#xff0c;与最后一个数交换 第二步再找出除最后一位美女外其它美女中的最高者&#xff0c;与倒数第二个美女交换位置 再找出除最…

k8s-pod详解

一、Pod基本概念&#xff1a; 1.pod介绍&#xff1a; Pod是kubernetes中最小的资源管理组件&#xff0c;Pod也是最小化运行容器化应用的资源对象。一个Pod代表着集群中运行的一个进程。kubernetes中其他大多数组件都是围绕着Pod来进行支撑和扩展Pod功能的&#xff0c;例如&am…

电赛经验分享——赛前准备

⏩ 大家好哇&#xff01;我是小光&#xff0c;想要成为系统架构师的嵌入式爱好者。 ⏩在之前的电赛中取得了省一的成绩&#xff0c;本文对电赛比赛前需要准备什么做一个经验分享。 ⏩感谢你的阅读&#xff0c;不对的地方欢迎指正。 加入小光嵌入式交流群&#xff08;qq群号&…

在线人才测评在企业招聘和大学生求职中的应用场景

每年的春招秋招&#xff0c;都是毕业生们忙着找工作的季节&#xff0c;相比社招来说&#xff0c;春招秋招是每个毕业生务必重视的机会&#xff0c;大厂名企毕竟名额有限&#xff0c;如果找到自己心仪的职业岗位&#xff0c;作为毕业生就必须提前准备&#xff0c;深入了解招聘的…

五管OTA输入极性快速判断

做CMFB还有负反馈的时候曾经在判断输入输出极性上吃了大亏&#xff0c;直接做实验波形正确就是输入正端&#xff0c;全差分就不用考虑这么多了 和弯折&#xff0c;形状类似7&#xff0c;相同方向输入正端&#xff0c;相反的就是输入负端&#xff0c;输出也是和输入负端一个方向…

【NLP】人机对话

概念 机器翻译就是用计算机把一种语言翻译成另外一种语言的技术 机器翻译的产生与发展 17 世纪&#xff0c;笛卡尔与莱布尼茨试图用统一的数字代码来编写词典 1930 机器脑 1933 苏联发明家特洛阳斯基用机械方法将一种语言翻译为另一种语言 1946 ENIAC 诞生 1949 机器翻译问题…

香蕉成熟度检测YOLOV8NANO

香蕉成熟度检测YOLOV8NANO&#xff0c;采用YOLOV8NANO训练&#xff0c;得到PT模型&#xff0c;然后转换成ONNX模型&#xff0c;让OEPNCV调用&#xff0c;从而摆脱PYTORCH依赖&#xff0c;支持C。python&#xff0c;安卓开发。能检测六种香蕉类型freshripe freshunripe overripe…

Vita-CLIP: Video and text adaptive CLIP via Multimodal Prompting

标题&#xff1a;Vita-CLIP: 通过多模态提示进行视频和文本自适应CLIP 源文链接&#xff1a;https://openaccess.thecvf.com/content/CVPR2023/papers/Wasim_Vita-CLIP_Video_and_Text_Adaptive_CLIP_via_Multimodal_Prompting_CVPR_2023_paper.pdfhttps://openaccess.thecvf.…

sw布尔减

可能最有效率还是草图边界线,然后用草图做分割

ue5 中ps使用记录贴

一、快捷键记录 放大图形 ctrlalt空格 放大图形 缩小视口 ctrl空格 ctrlD 取消选区 ctrlt缩小文字 w魔棒工具 选择魔棒的时候把容差打开的多一点 二、案例 移动文字 在相应的图层选择 移动文字 修改图片里的颜色 在通道里拷贝红色通道&#xff0c;复制红色通道粘贴给正常图…

记录Hbase出现HMaster一直初始化,日志打印hbase:meta,,1.1588230740 is NOT online问题的解决

具体错误 hbase:meta,,1.1588230740 is NOT online&#xff1b; state{1588230740 stateOPEN, ...... 使用 hbase 2.5.5 &#xff0c;hdfs和hbase分离两台服务器。 总过程 1. 问题发现 在使用HBase的程序发出无法进行插入到HBase操作日志后检查HBase状况。发现master节点和r…

大模型应用商业化落地关键:给企业带来真实的业务价值

2024 年被很多人称为大模型应用的元年&#xff0c;毫无疑问&#xff0c;大模型已经成为共识&#xff0c;下一步更急迫的问题也摆在了大家的面前——大模型到底能够用在哪&#xff1f;有哪些场景能落地&#xff1f;怎么做才能创造真正的价值&#xff1f; 在刚刚过去的 AICon 全…

【排序算法】快速排序(四个版本以及两种优化)含动图)

制作不易&#xff0c;三连支持一下吧&#xff01;&#xff01;&#xff01; 文章目录 前言一.快速排序Hoare版本实现二.快速排序挖坑法版本实现三.快速排序前后指针版本实现四.快速排序的非递归版本实现五.两种优化总结 前言 前两篇博客介绍了插入和选择排序&#xff0c;这篇博…

halcon配合yolov8导出onnx模型检测物体

1.工业上多数视觉开发都是用halcon开发的&#xff0c;halcon本身也有自己的深度学习网络&#xff0c;至于halcon如果不使用本身的深度学习&#xff0c;使用其他网络导出的onnx模型怎样配合使用&#xff1f;本文基于yolov8写一个列子。 2。创建输入数据的转换代码 #region 创建输…

Nginx高可用性架构:实现负载均衡与故障转移的探索

随着网络应用的不断发展和用户访问量的增长&#xff0c;如何确保系统的高可用性、实现负载均衡以及快速响应故障转移成为了每个运维和开发团队必须面对的挑战。Nginx作为一款高性能的HTTP和反向代理服务器&#xff0c;凭借其强大的功能和灵活的配置&#xff0c;成为了实现这些目…

题目:填空练习(指向指针的指针)

题目&#xff1a;填空练习&#xff08;指向指针的指针&#xff09; There is no nutrition in the blog content. After reading it, you will not only suffer from malnutrition, but also impotence. The blog content is all parallel goods. Those who are worried about …

【bugfix】/usr/local/bin/docker-compose:行1: html: 没有那个文件或目录

前言 在使用 docker-compose 管理容器化应用时&#xff0c;偶尔会遇到一些意想不到的错误&#xff0c;比如当尝试运行 docker-compose 命令时&#xff0c;终端非但没有展示预期的输出&#xff0c;反而出现类似网页错误的信息。这类问题通常与 docker-compose 的安装或配置有关…

首都师范大学聘请旅美经济学家向凌云为客座教授

2024年4月17日&#xff0c;首都师范大学客座教授聘任仪式在首都师范大学资源环境与旅游学院举行。首都师范大学资源环境与旅游学院院长吕拉昌主持了仪式&#xff0c;并为旅美经济学家向凌云教授颁发了聘书。 吕拉昌院长指出&#xff0c;要贯彻教育部产学研一体化战略&#xff0…

数据库工具类

public interface DbMapper {/*** 查询数据库类型*/String queryDbType(); }<select id"queryDbType" resultType"java.lang.String">select<choose><when test"_databaseId mysql">mysql</when><when test"_d…

虚拟机Centos扩展磁盘空间

虚拟机空间&#xff1a;现sda大小20G&#xff0c;因课程需要扩容 在虚拟机扩容中&#xff0c; 新增一块硬盘 和 直接在原有硬盘基础上扩容是一样的&#xff08;只不过在原有硬盘上扩容需要关机才可以执行&#xff09;&#xff1b; 但两者都最好先做数据备份或快照&#xff0c…