Log 标准库使用,设置日志格式(是内容而非打印顺序)、日志前缀、日志输出指向,得到一个 Log 对象;打开文件以及权限模式、读写文件,Json 序列化
Log 日志标准库
使用默认的 log 结构体对象
log.SetFlags 中设置的标志位,可以参考下面的 const 常量
1
2
3
4
5
6
7
8
9
10
11
关于 Log 标准库的方法使用案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
func main() {
// 1、打印普通类型的日志
log.Println("this line is info log")
// log.Panic 类型方法,会触发 Panic 导致程序直接结束,后面的不会运行
// log.Panicln("this line is panic error log")
// log.Fatal 类型方法,会触发 Fatal 导致程序直接结束,后面的不会运行
// log.Fatalln("this line is fatal log")
// 默认是 3 log.Flags() log.Ldate | log.Ltime
fmt.Println(log.Flags())
// 2、重新设定 log.Flags log.Llongfile 是打印当前绝对路径 , log.Lmicroseconds 是显示毫秒级的时间,log.Ldate 是显示日期
log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
fmt.Println(log.Flags())
log.Println("this log.Flags is 13")
fmt.Println("===============")
// 3、prefix 配置日志的前缀
fmt.Println(log.Prefix())
log.SetPrefix("RDS Service Status ====> ")
log.Println("[info] starting...")
// 4、log.SetOutput 配置日志的输出位置
log.SetOutput(os.Stdout)
log.Println("设置日志打印的位置...")
// 5、文件实现了 io.Writer 接口,放一个文件对象
file01, err := os.Create("./log.txt")
if err != nil {
fmt.Println(err)
}
log.SetOutput(file01) // 需要放入一个实现了 io.Writer 接口的对象
log.Println("当前日志打印的位置在 log.txt 中...")
// 6、用 os.Create 每次都是创建一个新的 log.txt 文件
file02, err := os.OpenFile("./log_OpenFile.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
fmt.Println(err)
}
log.SetOutput(file02)
log.Println("用 os.OpenFile 函数打开文件并写入日志...")
}
// 7、通常我们将日志的配置,写到 init() 函数中
func init() {
log.SetPrefix("ServiceTest 服务日志:")
log.SetFlags(log.Lshortfile | log.Lmicroseconds | log.Ldate)
if logFile, err := os.OpenFile("servicetest.log ", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666); err == nil {
log.SetOutput(logFile)
} else {
fmt.Println("ServiceTest 日志文件创建/读写错误:", err)
}
}
自定义 Logger 结构体对象
1
2
3
4
5
func main() {
// 如果想自己造出一个 logger 对象
logger := log.New(os.Stdout, "自定义的 Logger 对象写日志 ===> ", log.LstdFlags)
logger.Println("[info] xxxxx")
}
OpenFile/Open 文件打开 && 关闭操作
OpenFile() 方法的标志位(flag 参数)
1
2
3
4
5
6
7
8
9
10
11
12
const (
// Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
O_RDONLY int = syscall.O_RDONLY // open the file read-only.
O_WRONLY int = syscall.O_WRONLY // open the file write-only.
O_RDWR int = syscall.O_RDWR // open the file read-write.
// The remaining values may be or'ed in to control behavior.
O_APPEND int = syscall.O_APPEND // append data to the file when writing.
O_CREATE int = syscall.O_CREAT // create a new file if none exists.
O_EXCL int = syscall.O_EXCL // used with O_CREATE, file must not exist.
O_SYNC int = syscall.O_SYNC // open for synchronous I/O.
O_TRUNC int = syscall.O_TRUNC // truncate regular writable file when opened.
)
os.Open() 源代码返回值是 return OpenFile(name, O_RDONLY, 0) 可以看出其实就是调用了 OpenFile 并指定只读参数
1
2
3
4
5
6
7
8
9
10
func main() {
// 只读方式打开当前目录下的main.go文件
file, err := os.Open("./log.txt")
if err != nil {
fmt.Println("open file failed!, err:", err)
return
}
// 关闭文件
file.Close()
}
os.OpenFile() 打开文件是常用的方法
1
2
3
4
5
6
7
8
func main() {
file, err := os.OpenFile("./log.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
fmt.Println(err)
}
// 关闭文件
defer file.Close()
}
文件的读操作
file.Read()
首先使用 file.Read() 去读文件,我们需要一个 recieveByteSliceBuf 拿到 Read() 方法得到的 []byte 切片
1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
// 只读方式打开当前目录下的main.go文件
file, err := os.Open("./log.txt")
if err != nil {
fmt.Println("open file failed!, err:", err)
return
}
var receiveByteSliceBuf []byte = make([]byte, 2048)
n, err := file.Read(receiveByteSliceBuf)
fmt.Printf("一共读了 %v 个 字节 \n", n)
// 需要用 string() 把字节码转换成字符串
fmt.Println(string(receiveByteSliceBuf))
}
可以看到我们指定了 [] byte 的初始化长度,但如果一个文件我们不知道字节长度,该如何传这个参数?用 for break 就可以
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
// 只读方式打开当前目录下的main.go文件
file, err := os.Open("./log.txt")
if err != nil {
fmt.Println("open file failed!, err:", err)
return
}
for {
var receiveByteSliceBuf = make([]byte, 3)
_, err := file.Read(receiveByteSliceBuf)
if err == io.EOF {
break
}
fmt.Printf(string(receiveByteSliceBuf))
}
fmt.Println("读取文件结束...")
}
上面是将所有内容全部输出了,但是如果我想要一个接受整个文件内容的对象,该怎么办?如下的 content 就是接收了全部内容的 []byte 切片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func main() {
// 只读方式打开当前目录下的main.go文件
file, err := os.Open("./log.txt")
if err != nil {
fmt.Println("open file failed!, err:", err)
return
}
// 定义一个接受内容的 content
var content []byte
for {
var receiveByteSliceBuf = make([]byte, 3)
_, err := file.Read(receiveByteSliceBuf)
if err == io.EOF {
break
} else if err != nil {
println("读取文件错误:", err)
}
// 这里的是把读取到的长度为 3 的 receiveByteSliceBuf []byte 切片,追加到 content []byte 切片中
//需要将子切片打散,所以写作 receiveByteSliceBuf...
content = append(content, receiveByteSliceBuf...)
}
fmt.Printf("读取文件 %v 结束... 文件内容如下:\n", file.Name())
fmt.Println(string(content))
}
ioutil — 小文件
可以看到 file.Read() 自带方法读文件非常麻烦,我们可以用 ioutil 下的方法读取文件,更方便 ,但用于小文件
1
2
3
4
5
6
7
8
func main() {
// 一次性将文件读取出来,其实是就是调用了 Openfile
content, err := ioutil.ReadFile("./log.txt")
if err != nil {
fmt.Println(err)
}
fmt.Println(string(content))
}
bufio — 大文件
都是需要打开文件才能读取的,bufio 带缓存,可以读取大文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
file, err := os.Open("./log1.go")
if err != nil {
fmt.Println(file)
}
defer file.Close()
// 需要先打开文件,才能使用 bufio.NewReader,因为 bufio.NewReader 需要接受一个 file 对象
reader := bufio.NewReader(file) // 新建一个 Reader 对象,可以用 reader.ReadLine 方法按行读取文件
for { // 循环读取所有行
line, _, err := reader.ReadLine() // 按行读取文件
if err == io.EOF {
break
}
fmt.Println(string(line))
}
}
也可以根据自己定义的块([]byte 切片长度)读取文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
file, err := os.Open("./bufio.go")
if err != nil {
fmt.Println(err)
}
reader := bufio.NewReader(file)
var recByteSlice []byte = make([]byte, 5)
var content []byte
for {
_, err := reader.Read(recByteSlice) // Read() 方法两个返回值,第一个是本次读取的 []byte 长度
if err == io.EOF {
break
}
// 这里 recByteSlice... 后面三个点的意思是,将 []byte 打散追加给 content,需要 content 接收 append 方法的返回值
content = append(content, recByteSlice...)
}
fmt.Println("文件内容已全部 append 到 content 切片...")
fmt.Println(string(content))
}
文件的写操作
file.Write() && file.WriteString()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
//file, err := os.Create("./writeTest.txt")
file, err := os.OpenFile("writeTest.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
fmt.Println(err)
}
defer file.Close()
// 使用 file.Write 方法,需要传入一个 []byte 切片
var logWriteSlice []byte = []byte("RDS Service Status ====> 2022/07/28 14:40:50.637032 C:/Users/ethan/go/src/awesomeProject2/log1.go:41: 当前日志打印的位置在 log.txt 中...\n")
file.Write(logWriteSlice)
// 使用 file.Writestring,直接传入字符串
file.WriteString("WriteString1 ...\n")
file.WriteString("WriteString2 ...\n")
}
ioutil.WriteFile()
ioutil 的方便之处就是,读写都是可以一次性的
1
2
3
4
func main() {
// 内部封装了 OpenFile(name, O_WRONLY|O_CREATE|O_TRUNC, perm)
ioutil.WriteFile("writeTest.txt", []byte("This is www.itsky.tech"), 0666)
}
bufio.Writer()
1
2
3
4
5
6
7
8
9
10
11
12
func main() {
// BufIo NewWriter
file, err := os.OpenFile("writeTest_bufio.txt", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
if err != nil {
fmt.Println(err)
}
defer file.Close()
writer := bufio.NewWriter(file)
writer.WriteString("abcdefg\n")
// 必须要将内存缓冲区的数据,刷入硬盘
writer.Flush()
}
fmt.Fprintf()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
// 利用 fmt.Fprintf 函数写文件
file, err := os.OpenFile("./writeTest_Fprintf.txt", os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
fmt.Println(err)
}
serviceName := "RDS"
fprintTest01 := "%v writeTest For Fprintf Func\n"
fmt.Fprintf(file, fprintTest01, serviceName)
fprintTest02 := []string{"OKay you are the hero", "Noah fang boat", "Go out the house"}
for _, value := range fprintTest02 {
fmt.Fprintln(file, value)
}
defer file.Close()
}
文件关闭操作
参考上述链接,可以得知 defer file.Close() 可能不是完全正确的关闭文件姿势
并发写文件 - (Goroutine)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 写随机数函数
func WriterRole(wg *sync.WaitGroup, writeChan chan interface{}) {
writeChan <- rand.Intn(100)
defer wg.Done()
}
// 写入函数,循环取 writeChan 信道中的数据,直到 writeChan 信道关闭
func FileReceiver(writeChan chan interface{}, file io.Writer, isDone chan interface{}) {
for res := range writeChan {
fmt.Fprintln(file, res)
}
isDone <- "Done"
}
// 主函数
func main() {
var writeChan chan interface{} = make(chan interface{})
var isDone chan interface{} = make(chan interface{})
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go WriterRole(&wg, writeChan)
}
// 让 wg.Wait() 放在协程中等待的原因是,如果放在主 Main 函数中,writeChan 永远写不完,而 wg.Wait() 就是等待 100 歌数据传入 writeChan ,是会死锁的
go func() {
wg.Wait()
close(writeChan)
}()
file, err := os.OpenFile("goroutine_writeTest.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Println(err)
}
// 得让文件写入起 Goroutine ,否则会阻塞 writeChan 信道
go FileReceiver(writeChan, file, isDone)
// 当写入函数中的 for 取完了 writeChan 中的数据,那么就会向 isDone 信道中传入数据,下面 <-isDone 就可以拿到数据,不会死锁
<-isDone
}
Struct <=> Json 序列化和反序列化
Json 的序列化方法,用 json.Marshal 方法,传入结构体参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Wife string `json:"-"` // 这样表示,不将该字段做序列化输出
Hobby []string `json:",omitempty"` // 这样表示,如果该字段有值则做序列化输出,如果没有则不做序列化输出
// Hobby []string `json:"hobby,omitempty"` // 如果想要序列化后的 Hobby 小写,需要这样写
}
func main() {
// Hobby 没有赋值的结构体,序列化不会有 Hobby 字段
var one = Person{Name: "ethan", Age: 12, Wife: "may"}
// Hobby 有赋值的结构体
var two = Person{Name: "may", Age: 13, Wife: "ethan", Hobby: []string{"zzz", "ddd"}}
// 1、Marshal 把对象转成 Json 字符串
res01, err := json.Marshal(one)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(res01))
res02, err := json.Marshal(two)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(res02))
}
反序列化的例子,使用 Unmarshal,需要传入字节切片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Wife string `json:"-"` // 这样表示,不将该字段做序列化输出
Hobby []string `json:",omitempty"` // 这样表示,如果该字段有值则做序列化输出,如果没有则不做序列化输出
// Hobby []string `json:"hobby,omitempty"` // 如果想要序列化后的 Hobby 小写,需要这样写
}
func main() {
// 2、Unmarshal 把 Json 字符串转成对象
var three Person
stringOfJson := "{\"name\":\"ethan\",\"age\":12}"
json.Unmarshal([]byte(stringOfJson), &three)
fmt.Println(three)
}
Map <=> Json 数据的序列化/反序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Map 数据类型序列化
func main() {
// 首先是序列化 MAP => JSON
var mapJson = map[string]interface{}{"name": "小王", "address": "earth", "Wife": "May"}
b, err := json.Marshal(mapJson)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(b))
// 然后是反序列化 JSON => MAP
var recMap map[string]interface{}
var jsonString string = string(b)
json.Unmarshal([]byte(jsonString), &recMap)
// json.Unmarshal(b, &recMap) 其实直接传入 b 就行,但是模拟用上述代码
fmt.Println(recMap)
for key, value := range recMap {
fmt.Println(key, "=>", value)
}
}