本文目錄一覽:
- 1、golang logger輸出格式怎麼修改
- 2、基礎知識 – Golang 中的格式化輸入輸出
- 3、golang在將struct轉成json字元串的時候如何將日期類型的數據格式化?
- 4、golang 獲取時間精確能到納秒嗎
golang logger輸出格式怎麼修改
1.Logger結構
首先來看下類型Logger的定義:
type Logger struct {
mu sync.Mutex // ensures atomic writes; protects the following fields
prefix string // prefix to write at beginning of each line
flag int // properties
out io.Writer // destination for output
buf []byte // for accumulating text to write
}
主要有5個成員,其中3個我們比較熟悉,分別是表示Log前綴的 “prefix”,表示Log頭標籤的 “flag” ,以及Log的輸出目的地out。 buf是一個位元組數組,主要用來存放即將刷入out的內容,相當於一個臨時緩存,在對輸出內容進行序列化時作為存儲目的地。 mu是一個mutex主要用來作線程安全的實習,當有多個goroutine同時往一個目的刷內容的時候,通過mutex保證每次寫入是一條完整的信息。
2.std及整體結構
在前一篇文章中我們提到了log模塊提供了一套包級別的簡單介面,使用該介面可以直接將日誌內容列印到標準錯誤。那麼該過程是怎麼實現的呢?其實就是通過一個內置的Logger類型的變數 “std” 來實現的。該變數使用:
var std = New(os.Stderr, “”, LstdFlags)
進行初始化,默認輸出到系統的標準輸出 “os.Stderr” ,前綴為空,使用日期加時間作為Log抬頭。
當我們調用 log.Print的時候是怎麼執行的呢?我們看其代碼:
func Print(v …interface{}) {
std.Output(2, fmt.Sprint(v…))
}
這裡實際就是調用了Logger對象的 Output方法,將日誌內容按照fmt包中約定的格式轉義後傳給Output。Output定義如下 :
func (l *Logger) Output(calldepth int, s string) error
其中s為日誌沒有加前綴和Log抬頭的具體內容,xxxxx 。該函數執行具體的將日誌刷入到對應的位置。
3.核心函數的實現
Logger.Output是執行具體的將日誌刷入到對應位置的方法。
該方法首先根據需要獲得當前時間和調用該方法的文件及行號信息。然後調用formatHeader方法將Log的前綴和Log抬頭先格式化好 放入Logger.buf中,然後再將Log的內容存入到Logger.buf中,最後調用Logger.out.Write方法將完整的日誌寫入到輸出目的地中。
由於寫入文件以及拼接buf的過程是線程非安全的,因此使用mutex保證每次寫入的原子性。
l.mu.Lock()
defer l.mu.Unlock()
將buf的拼接和文件的寫入放入這個後面,使得在多個goroutine使用同一個Logger對象是,不會弄亂buf,也不會雜糅的寫入。
該方法的第一個參數最終會傳遞給runtime.Caller的skip,指的是跳過的棧的深度。這裡我記住給2就可以了。這樣就會得到我們調用log 是所處的位置。
在golang的注釋中說鎖住 runtime.Caller的過程比較重,這點我還是不很了解,只是從代碼中看到其在這裡把鎖打開了。
if l.flag(Lshortfile|Llongfile) != 0 {
// release lock while getting caller info – it『s expensive.
l.mu.Unlock()
var ok bool
_, file, line, ok = runtime.Caller(calldepth)
if !ok {
file = “???”
line = 0
}
l.mu.Lock()
}
在formatHeader裡面首先將前綴直接複製到Logger.buf中,然後根據flag選擇Log抬頭的內容,這裡用到了一個log模塊實現的 itoa的方法,作用類似c的itoa,將一個整數轉換成一個字元串。只是其轉換後將結果直接追加到了buf的尾部。
縱觀整個實現,最值得學習的就是線程安全的部分。在什麼位置合適做怎樣的同步操作。
4.對外介面的實現
在了解了核心格式化和輸出結構後,在看其封裝就非常簡單了,幾乎都是首先用Output進行日誌的記錄,然後在必要的時候 做os.exit或者panic的操作,這裡看下Fatal的實現。
func (l *Logger) Fatal(v …interface{}) {
l.Output(2, fmt.Sprint(v…))
os.Exit(1)
}
// Fatalf is equivalent to l.Printf() followed by a call to os.Exit(1).
func (l *Logger) Fatalf(format string, v …interface{}) {
l.Output(2, fmt.Sprintf(format, v…))
os.Exit(1)
}
// Fatalln is equivalent to l.Println() followed by a call to os.Exit(1).
func (l *Logger) Fatalln(v …interface{}) {
l.Output(2, fmt.Sprintln(v…))
os.Exit(1)
}
這裡也驗證了我們之前做的Panic的結果,先做輸出日誌操作。再進行panic。
基礎知識 – Golang 中的格式化輸入輸出
【格式化輸出】
// 格式化輸出:將 arg 列表中的 arg 轉換為字元串輸出
// 使用動詞 v 格式化 arg 列表,非字元串元素之間添加空格
Print(arg列表)
// 使用動詞 v 格式化 arg 列表,所有元素之間添加空格,結尾添加換行符
Println(arg列表)
// 使用格式字元串格式化 arg 列表
Printf(格式字元串, arg列表)
// Print 類函數會返回已處理的 arg 數量和遇到的錯誤信息。
【格式字元串】
格式字元串由普通字元和佔位符組成,例如:
“abc%+ #8.3[3]vdef”
其中 abc 和 def 是普通字元,其它部分是佔位符,佔位符以 % 開頭(註:%% 將被轉義為一個普通的 % 符號,這個不算開頭),以動詞結尾,格式如下:
%[旗標][寬度][.精度][arg索引]動詞
方括弧中的內容可以省略。
【旗標】
旗標有以下幾種:
空格:對於數值類型的正數,保留一個空白的符號位(其它用法在動詞部分說明)。
0 :用 0 進行寬度填充而不用空格,對於數值類型,符號將被移到所有 0 的前面。
其中 “0” 和 “-” 不能同時使用,優先使用 “-” 而忽略 “0”。
【寬度和精度】
「寬度」和「精度」都可以寫成以下三種形式:
數值 | * | arg索引*
其中「數值」表示使用指定的數值作為寬度值或精度值,「 」表示使用當前正在處理的 arg 的值作為寬度值或精度值,如果這樣的話,要格式化的 arg 將自動跳轉到下一個。「arg索引 」表示使用指定 arg 的值作為寬度值或精度值,如果這樣的話,要格式化的 arg 將自動跳轉到指定 arg 的下一個。
寬度值:用於設置最小寬度。
精度值:對於浮點型,用於控制小數位數,對於字元串或位元組數組,用於控制字元數量(不是位元組數量)。
對於浮點型而言,動詞 g/G 的精度值比較特殊,在適當的情況下,g/G 會設置總有效數字,而不是小數位數。
【arg 索引】
「arg索引」由中括弧和 arg 序號組成(就像上面示例中的 [3]),用於指定當前要處理的 arg 的序號,序號從 1 開始:
‘[‘ + arg序號 + ‘]’
【動詞】
「動詞」不能省略,不同的數據類型支持的動詞不一樣。
[通用動詞]
v:默認格式,不同類型的默認格式如下:
布爾型:t
整 型:d
浮點型:g
複數型:g
字元串:s
通 道:p
指 針:p
無符號整型:x
T:輸出 arg 的類型而不是值(使用 Go 語法格式)。
[布爾型]
t:輸出 true 或 false 字元串。
[整型]
b/o/d:輸出 2/8/10 進位格式
x/X :輸出 16 進位格式(小寫/大寫)
c :輸出數值所表示的 Unicode 字元
q :輸出數值所表示的 Unicode 字元(帶單引號)。對於無法顯示的字元,將輸出其轉義字元。
U :輸出 Unicode 碼點(例如 U+1234,等同於字元串 “U+%04X” 的顯示結果)
對於 o/x/X:
如果使用 “#” 旗標,則會添加前導 0 或 0x。
對於 U:
如果使用 “#” 旗標,則會在 Unicode 碼點後面添加相應的 ‘字元’(前提是該字元必須可顯示)
[浮點型和複數型]
b :科學計數法(以 2 為底)
e/E:科學計數法(以 10 為底,小寫 e/大寫 E)
f/F:普通小數格式(兩者無區別)
g/G:大指數(指數 = 6)使用 %e/%E,其它情況使用 %f/%F
[字元串或位元組切片]
s :普通字元串
q :雙引號引起來的 Go 語法字元串
x/X:十六進位編碼(小寫/大寫,以位元組為元素進行編碼,而不是字元)
對於 q:
如果使用了 “+” 旗標,則將所有非 ASCII 字元都進行轉義處理。
如果使用了 “#” 旗標,則輸出反引號引起來的字元串(前提是
字元串中不包含任何製表符以外的控制字元,否則忽略 # 旗標)
對於 x/X:
如果使用了 ” ” 旗標,則在每個元素之間添加空格。
如果使用了 “#” 旗標,則在十六進位格式之前添加 0x 前綴。
[指針類型]
p :帶 0x 前綴的十六進位地址值。
[符合類型]
複合類型將使用不同的格式輸出,格式如下:
結 構 體:{欄位1 欄位2 …}
數組或切片:[元素0 元素1 …]
映 射:map[鍵1:值1 鍵2:值2 …]
指向符合元素的指針:{}, [], map[]
複合類型本身沒有動詞,動詞將應用到複合類型的元素上。
結構體可以使用 “+v” 同時輸出欄位名。
【注意】
1、如果 arg 是一個反射值,則該 arg 將被它所持有的具體值所取代。
2、如果 arg 實現了 Formatter 介面,將調用它的 Format 方法完成格式化。
3、如果 v 動詞使用了 # 旗標(%#v),並且 arg 實現了 GoStringer 介面,將調用它的 GoString 方法完成格式化。
如果格式化操作指定了字元串相關的動詞(比如 %s、%q、%v、%x、%X),接下來的兩條規則將適用:
4。如果 arg 實現了 error 介面,將調用它的 Error 方法完成格式化。
5。如果 arg 實現了 string 介面,將調用它的 String 方法完成格式化。
在實現格式化相關介面的時候,要避免無限遞歸的情況,比如:
type X string
func (x X) String() string {
return Sprintf(“%s”, x)
}
在格式化之前,要先轉換數據類型,這樣就可以避免無限遞歸:
func (x X) String() string {
return Sprintf(“%s”, string(x))
}
無限遞歸也可能發生在自引用數據類型上面,比如一個切片的元素引用了切片自身。這種情況比較罕見,比如:
a := make([]interface{}, 1)
a[0] = a
fmt.Println(a)
【格式化輸入】
// 格式化輸入:從輸入端讀取字元串(以空白分隔的值的序列),
// 並解析為具體的值存入相應的 arg 中,arg 必須是變數地址。
// 字元串中的連續空白視為單個空白,換行符根據不同情況處理。
// \r\n 被當做 \n 處理。
// 以動詞 v 解析字元串,換行視為空白
Scan(arg列表)
// 以動詞 v 解析字元串,換行結束解析
Scanln(arg列表)
// 根據格式字元串中指定的格式解析字元串
// 格式字元串中的換行符必須和輸入端的換行符相匹配。
Scanf(格式字元串, arg列表)
// Scan 類函數會返回已處理的 arg 數量和遇到的錯誤信息。
【格式字元串】
格式字元串類似於 Printf 中的格式字元串,但下面的動詞和旗標例外:
p :無效
T :無效
e/E/f/F/g/G:功能相同,都是掃描浮點數或複數
s/v :對字元串而言,掃描一個被空白分隔的子串
對於整型 arg 而言,v 動詞可以掃描帶有前導 0 或 0x 的八進位或十六進位數值。
寬度被用來指定最大掃描寬度(不會跨越空格),精度不被支持。
如果 arg 實現了 Scanner 介面,將調用它的 Scan 方法掃描相應數據。只有基礎類型和實現了 Scanner 介面的類型可以使用 Scan 類方法進行掃描。
【注意】
連續調用 FScan 可能會丟失數據,因為 FScan 中使用了 UnreadRune 對讀取的數據進行撤銷,而參數 io.Reader 只有 Read 方法,不支持撤銷。比如:
golang在將struct轉成json字元串的時候如何將日期類型的數據格式化?
如果你想輸出的時間是YYYY-MM-DD的話
要在使用json數據化之前自己處理時間
type Article struct {
Id int
Title string
CreateTimeStr string
}
然後要將之前的時間轉過來
Article.CreateTimeStr = Createdatetime.Format(“2006-01-02”)
最後序列化JSON就是YYYY-MM-DD
這是最簡單的方法
golang 獲取時間精確能到納秒嗎
這樣。不過只是個精確到納秒的計時器,不是精確到納秒的當前時間。windows好像只能拿到ms精度的當前時間吧,不是很清楚。
package main
import (
“syscall”
“time”
“unsafe”
)
func NewStopWatch() func() time.Duration {
var QPCTimer func() func() time.Duration
QPCTimer = func() func() time.Duration {
lib, _ := syscall.LoadLibrary(“kernel32.dll”)
qpc, _ := syscall.GetProcAddress(lib, “QueryPerformanceCounter”)
qpf, _ := syscall.GetProcAddress(lib, “QueryPerformanceFrequency”)
if qpc == 0 || qpf == 0 {
return nil
}
var freq, start uint64
syscall.Syscall(qpf, 1, uintptr(unsafe.Pointer(freq)), 0, 0)
syscall.Syscall(qpc, 1, uintptr(unsafe.Pointer(start)), 0, 0)
if freq = 0 {
return nil
}
freqns := float64(freq) / 1e9
return func() time.Duration {
var now uint64
syscall.Syscall(qpc, 1, uintptr(unsafe.Pointer(now)), 0, 0)
return time.Duration(float64(now-start) / freqns)
}
}
var StopWatch func() time.Duration
if StopWatch = QPCTimer(); StopWatch == nil {
// Fallback implementation
start := time.Now()
StopWatch = func() time.Duration { return time.Since(start) }
}
return StopWatch
}
func main() {
// Call a new stop watch to create one from this moment on.
watch := NewStopWatch()
// Do some stuff that takes time.
time.Sleep(1)
// Call the stop watch itself and it will return a time.Duration
dur := watch()
}
這和語言沒關係,操作系統要提供這樣的原語。linux和windows都是可以的。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/270648.html