iT邦幫忙

2023 iThome 鐵人賽

DAY 25
0
自我挑戰組

Go in 3o系列 第 25

[Day25] Go in 30 - Debug

  • 分享至 

  • xImage
  •  

一、本篇提要

接下來要介紹的是Go的除錯基本方法,將會介紹到格式化訊息、log、單元測試(unit test)。

  • 以 fmt 套件做格式化輸出
  • 使用log

二、fmt格式化

2.1 一些格式化表格

函數 描述
%v 值的預設格式
%+v 輸出結構體的欄位名和值
%#v 輸出 Go 語言的語法表示
%T 輸出值的類型
%% 輸出百分比符號 '%'
%b 整數以二進制表示
%c 整數對應的 Unicode 碼點
%d 整數以十進制表示
%o 整數以八進制表示
%q 單引號包圍的字符字面值或數字
%x 整數以十六進制表示,使用小寫字母 a-f
%X 整數以十六進制表示,使用大寫字母 A-F
%U Unicode 表示,例如 U+1234
%f 浮點數,無小數點及指數組成
%F 等同 %f
%e 浮點數,有指數組成
%E 浮點數,有指數組成
%g 根據情況選擇 %e 或 %f 以產生更簡短的輸出
%G 根據情況選擇 %E 或 %F 以產生更簡短的輸出
%s 字符串
%q 雙引號包圍的字符串
%p 指針的十六進制表示

痾....很多,但用久就習慣。

以下一個簡單範例 :

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
    fmt.Printf("%d + %d = %d\n", 3, 4, 3+4)
    fmt.Printf("Float: %f\n", 3.14159)
    fmt.Printf("String: %s\n", "Go Programming")
    fmt.Printf("Boolean: %t\n", true)
    fmt.Printf("Hex: %#x\n", 255)
    fmt.Printf("Percent: %%\n")
}

https://ithelp.ithome.com.tw/upload/images/20231004/20162693SScValPUzj.png

2.2 控制浮點數精度

3.74567 ---> %.2f ----> 3.75
3.74567 ---> %.3f ----> 3.746
3.74567 ---> %.4f ----> 3.7457
3.74567 ---> %.5f ----> 3.74567

浮點數包含整數部分、小數,例如 : %10.2f 就代表分配10字元給浮點數,包括1個小數點與2個小數位,這樣一來就是 7個整數 + 1小數點 + 2小數位 = 10 字元長度,若長度不足,則會把數字向右對齊,不足部分以空白填補。

範例 :

package main

import "fmt"

func main() {
    v := 1234.5678915 //原始數字
    fmt.Printf("%10.0f\n", v)
    fmt.Printf("%10.1f\n", v)
    fmt.Printf("%10.2f\n", v)
    fmt.Printf("%10.3f\n", v)
    fmt.Printf("%10.4f\n", v)
    fmt.Printf("%10.5f\n", v)
}

輸出結果 :

https://ithelp.ithome.com.tw/upload/images/20231004/20162693yH03BfRwG9.png

2.3 strconv.FormatFloat() 格式化浮點數

strconv 套件功能是將其他型別的值轉為字串,但FormatFloat()在轉換過程中,也可以指定要四捨五入的小數位數,實際上,它的底層運作機制和fmt.Printf()與Sprintf()是一樣的。

<格式化 string> := strconv.FormatFloat(<float64 浮點數>, <格式化符號>, <小數位數>, <原始資料位元數>)

傳入浮點數為float64型別,格式化符號參數則和fmt的格式化動詞動詞,只是改用字元形式且且不寫"%",最後一個參數則用來表示浮點數原始值是32或64位元。

strconv.FormatFloat(1234.5678915, "f", "2", 64) //轉回 1234.67

處理速度快且消耗記憶體更少,所以當程式要考慮運作效率及記憶體用量,使用strconv就可能是好主意。

三、使用log

使用Go語言log套件,使用 log.Println() 來記錄。

3.1 使用log.Println()

package main

import (
    "errors"
    "log"
)

func main() {
    log.Println("Start of our app")
    err := errors.New("application aborted!")
    if err != nil {
        log.Println(err)
    }
    log.Println("End of our app")
}

輸出結果 :

https://ithelp.ithome.com.tw/upload/images/20231005/20162693aYpG5M2sKQ.png

3.2 SetFlags()自訂log套件的日誌訊息格式


func main() {
    log.SetFlags(log.Ldate | log.Lmicroseconds | log.Llongfile)
    log.Println("start of out app")
    err := errors.New("application aborted!")
    if err != nil {
        log.Println(err)
    }
    log.Println("End of our app")
}

輸出結果 :

https://ithelp.ithome.com.tw/upload/images/20231005/201626936RAGyTfYSK.png

log.SetFlags() 當中放的,就是log套件提供的常數。

而我們就是在設置日誌的輸出格式:

log.SetFlags(log.Ldate | log.Lmicroseconds | log.Llongfile)

使用 SetFlags 方法來定義日誌輸出的格式。這裡我們使用了三個標誌:

  • log.Ldate: 在每條日誌消息中包含日期。
  • log.Lmicroseconds: 在每條日誌消息中包含時間和微秒。
  • log.Llongfile: 在每條日誌消息中包含完整的文件路徑和行號。

log套件的常數 :

const (
	Ldate         = 1 << iota     // 本地時區的日期:2009/01/23
	Ltime                         // 本地時區的時間:01:23:23
	Lmicroseconds                 // 微秒的解析度:01:23:23.123123。假設有Ltime。
	Llongfile                     // 完整的文件名和行號:/a/b/c/d.go:23
	Lshortfile                    // 最終的文件名元素和行號:d.go:23。覆蓋Llongfile
	LUTC                          // 如果設置了Ldate或Ltime,使用UTC而不是本地時區
	Lmsgprefix                    // 如果又"前綴詞",將它挪到使用者自己的訊息前面
	LstdFlags     = Ldate | Ltime // logger的預設
)

3.3 使用 log.Fatal() 和 log.Panic() 紀錄嚴重錯誤

當你的應用程式遇到無法恢復的嚴重錯誤時,你可能想要立即終止程序或引發panic。在這種情況下,你可以使用 log 套件的 Fatal() 和 Panic() 函數。

使用 log.Fatal() 和其相關函數 (Fatalln(), Fatalf()) 記錄嚴重錯誤:
這些函式的功能與 log 和 fmt 的 Print()、Println()、和 Printf() 類似。但它們的特點是:在輸出訊息後,它們會**立即呼叫 os.Exit(1) 以終止程式。**這意味著當你使用 Fatal() 系列函數時,你的程式會在那裡終止,且不會執行任何後續。

使用 log.Panic() 和其相關函數 (Panicln(), Panicf()) 記錄嚴重錯誤:
這些函式的功能與 Fatal() 系列相似,但與之不同的是,它們會引發 panic 而不是終止程式。Panic是Go中的一個特殊機制,它可以中斷常規的控制流程。當panic發生時,你可以使用 recover() 函數來攔截並處理它,從而避免程式終止。但如果你不攔截 panic,它將導致程式終止。值得注意的是,os.Exit() 是無法被 recover() 捕獲的。

總之,當你的程式遇到嚴重錯誤時,你可以使用上述函數來記錄錯誤訊息。你可以選擇立即終止程式或引發panic,取決於你是否想給使用者或其他部分的程式一個恢復或處理錯誤的機會。

package main

import (
	"log"
	"os"
    "fmt"
)

func main() {
	file, err := os.Open("nonexistent_file.txt")
	if err != nil {
		log.Fatal("Error opening the file: ", err)
	}
	// 若上方發生錯誤,以下的代碼不會執行
    fmt.Println("can't reach if error occur")
	defer file.Close()

	// ... 其他程式邏輯
}

輸出結果 :

https://ithelp.ithome.com.tw/upload/images/20231005/20162693Q5rMkfe0xv.png

3.4 建立自訂 logger 物件

目前為止我們都是用log套件提供的標準套件(standard log),我們也可以建立自己的(多重)logger,以便針對不同的情境輸出。

<logger> := log.New(<io.Writer 介面>, <前綴詞>, <旗標>)

標準 log 使用第一個參數會使用 os.Stdout,這個符合 io.Writer介面的物件其實就是將訊息印出,,

創建自己的log :

package main

import (
	"log"
	"os"
)

func main() {
	// 創建一個寫入文件的logger
	file, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		log.Fatal("Failed to open log file:", err)
	}
	defer file.Close()

	customLogger := log.New(file, "CUSTOM: ", log.Ldate|log.Ltime|log.Lshortfile)

	// 使用自定義的logger輸出訊息
	customLogger.Println("This is a custom log message.")

	// 使用標準logger輸出訊息
	log.Println("This is a standard log message.")
}

在上面的範例中,我們首先創建了一個名為 "app.log" 的文件,用於將日誌信息寫入其中。然後,我們使用 log.New() 方法創建一個自定義的 logger,指定輸出到先前創建的文件,並設置了一個前綴詞 "CUSTOM: " 和一些輸出格式標誌。

這樣,當你使用 customLogger 來輸出訊息時,它會被寫入 "app.log" 文件,而標準的 log 套件則會輸出到標準輸出(通常是終端或控制台)。這允許你針對不同的情境或不同的輸出目標使用不同的 logger。

3.5 io.Writer 介面 是什麼?

io.Writer 是 Go 語言中的一個基本介面,它表示任何對象都可以進行寫入操作。這個介面非常簡單,它只包含一個 Write 方法,如下所示:

type Writer interface {
    Write(p []byte) (n int, err error)
}

這個 Write 方法接受一個 byte slice p 作為參數,並返回兩個值:一個是成功寫入的 bytes 數,另一個是可能發生的錯誤。

由於 io.Writer 介面只有這麼一個方法,這意味著在 Go 中有很多物件都實現了這個介面,如下所示:

  • os.File:你可以使用文件對象(例如從 os.Open 或 os.Create 獲得)來寫入資料。
  • bytes.Buffer:一個在內存中儲存的可變大小的 buffer,可以用於存儲和操縱 bytes。
  • http.ResponseWriter:在建立 web 伺服器時,用於寫入 HTTP 回應的體部分。
    ... 以及許多其他的類型。

當你使用 log.New() 創建一個新的 logger 時,你需要提供一個實現了 io.Writer 介面的物件作為輸出目標。這樣,logger 就知道它需要將 log 訊息寫入哪裡。例如,你可以將 log 訊息寫入一個文件、一個內存 buffer 或者任何其他的 io.Writer 介面的實現。

以上就是有關使用fmt、log、自訂log去debug、記錄錯誤,下一篇來講講單元測試。


上一篇
[Day24] Go in 30 - 套件(package) - 第三方模組或套件
下一篇
[Day26] Go in 30 - Debug - 單元測試(unit test)
系列文
Go in 3o30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言