如何使用 Go進(jìn)行日志分析,并生成excel報(bào)表?
該使用什么工具去分析呢。最后還要生成excel表格。哇,給我愁壞了。
所以我開(kāi)始并沒(méi)有直接去做需求,而是去查資料、問(wèn)同事、朋友,怎么做日志分析。
確實(shí)搜到了一些日志分析的方法:awk、python。無(wú)疑是用腳本來(lái)做。但是我對(duì)這些不太熟悉呀,而且只有一下午的時(shí)間去做。最后我選擇了使用golang來(lái)做。相比于其他,我對(duì)golang更熟悉。
確定了語(yǔ)言,我就開(kāi)始分析日志了,下面我就來(lái)詳細(xì)介紹一下我是怎么使用go完成的日志分析,并成功生成excel表格。
#?1. 前期準(zhǔn)備
因?yàn)楣镜膌og不能在這里直接展示,所以本次教程我自己生成了幾個(gè)測(cè)試log。
{"httpRequest":{"request":"method:post,path:/api/user/login"},"params":{"query":"username=asong&password=123456"},"timings":{"evalTotalTime":0.420787431}}
{"httpRequest":{"request":"method:post,path:/api/user/login"},"params":{"query":"username=asong&password=123456"},"timings":{"evalTotalTime":0.420787431}}
{"httpRequest":{"request":"method:post,path:/api/user/login"},"params":{"query":"username=asong&password=123456"},"timings":{"evalTotalTime":0.420787431}}
{"httpRequest":{"request":"method:post,path:/api/user/login"},"params":{"query":"username=asong&password=123456"},"timings":{"evalTotalTime":0.420787431}}
{"httpRequest":{"request":"method:post,path:/api/user/login"},"params":{"query":"username=asong&password=123456"},"timings":{"evalTotalTime":0.420787431}}
{"httpRequest":{"request":"method:post,path:/api/user/login"},"params":{"query":"username=asong&password=123456"},"timings":{"evalTotalTime":0.420787431}}
{"httpRequest":{"request":"method:post,path:/api/user/login"},"params":{"query":"username=asong&password=123456"},"timings":{"evalTotalTime":0.420787431}}
{"httpRequest":{"request":"method:post,path:/api/user/login"},"params":{"query":"username=asong&password=123456"},"timings":{"evalTotalTime":0.420787431}}
{"httpRequest":{"request":"method:post,path:/api/user/login"},"params":{"query":"username=asong&password=123456"},"timings":{"evalTotalTime":0.420787431}}
{"httpRequest":{"request":"method:post,path:/api/user/login"},"params":{"query":"username=asong&password=123456"},"timings":{"evalTotalTime":0.420787431}}
這些log正常都在一行的,因?yàn)閙arkdown顯示問(wèn)題,顯示了多行。
#?2. 日志分析
分析之前,先看一下我們的需求:分析每個(gè)請(qǐng)求的次數(shù),查詢參數(shù),平均時(shí)間。
確定了需求,下面我們開(kāi)始對(duì)日志進(jìn)行分析。每一行代表一個(gè)完整的日志請(qǐng)求。每一行日志都是一個(gè)json字符串,這樣看起來(lái)確實(shí)不方便,我們格式化一下來(lái)看一下。
{
????"httpRequest":{
????????"request":"method:post,path:/api/user/login"
????},
????"params":{
????????"query":"username=asong&password=123456"
????},
????"timings":{
????????"evalTotalTime":0.420787431
????}
}
這樣看起來(lái)就很方便了,層次結(jié)構(gòu)一眼就能看出來(lái)。我們要統(tǒng)計(jì)請(qǐng)求的次數(shù),可以通過(guò)requrst這個(gè)字段判斷是否是同一個(gè)請(qǐng)求。query這個(gè)字段代表的是查詢參數(shù),evalTotalTime這個(gè)字段需要求和,然后求出平均數(shù)。日志分析好了,下面就是實(shí)現(xiàn)部分了。
#?3. 代碼實(shí)現(xiàn)
?代碼實(shí)現(xiàn)日志分析
這里我使用一個(gè)map來(lái)存放不同的請(qǐng)求,以請(qǐng)求作為key,請(qǐng)求次數(shù)、時(shí)間等作為value,不過(guò)這里存的時(shí)間所有請(qǐng)求的時(shí)間和,統(tǒng)計(jì)好所有請(qǐng)求次數(shù)與時(shí)間和后再計(jì)算平均時(shí)間。這樣所有分析好的數(shù)據(jù)就都在map里了,最后可針對(duì)這個(gè)map進(jìn)行excel導(dǎo)出,是不是很完美,哈哈。
定義map,需要統(tǒng)計(jì)的字段用struct封裝。
var?(
?result?map[string]*requestBody
?analysis?map[string]*requestBody
)
type?requestBody?struct?{
?count?int32
?query?string
?time?float64
}
因?yàn)槿罩疚募幸恍写硪粋€(gè)完整的日志,所以我們可以按行讀取日志,然后分析處理。
func?openFile()?*os.File?{
?file,err?:=?os.Open("./request.log")
?if?err?!=?nil{
??log.Println("open?log?err:?",err)
?}
?return?file
}
func?logDeal(file?*os.File)??{
?//?按行讀取
?br?:=?bufio.NewReader(file)
?for{
??line,_,err?:=?br.ReadLine()
??//?file?read?complete
??if?err?==?io.EOF{
???log.Println("file?read?complete")
???return
??}
??//json?deal
??var?data?interface{}
??err?=?json.Unmarshal(line,&data)
??if?err?!=?nil{
???fmt.Errorf("json?marshal?error")
??}
??deal(data)
?}
}
按行讀取好數(shù)據(jù)后,開(kāi)始對(duì)每一條日志進(jìn)行分析,提取字段??梢允褂胓olang的
json.Unmarshal,配合類型斷言,分析出每一個(gè)字段做處理。
func?deal(data?interface{})??{
?var?request?string
?var?query?string
?var?time?float64
?value,ok?:=?data.(map[string]interface{})
?if?ok{
??for?k,v?:=?range?value{
???if?k?==?"httpRequest"{
????switch?v1?:=?v.(type)?{
????case?map[string]interface{}:
?????for?k1,v11?:=?range?v1{
??????if?k1?==?"request"{
???????switch?val?:=?v11.(type)?{
???????case?string:
????????request?=?val
????????//fmt.Println(request)
???????}
??????}
?????}
????}
???}
???if?k?==?"params"{
????switch?v1?:=?v.(type)?{
????case?map[string]interface{}:
?????for?k1,v11?:=?range?v1{
??????if?k1?==?"query"{
???????switch?val?:=?v11.(type)?{
???????case?string:
????????query?=?val
????????//fmt.Println(query)
???????}
??????}
?????}
????}
???}
???if?k?==?"timings"{
????switch?v1?:=?v.(type)?{
????case?map[string]interface{}:
?????for?k1,v11?:=?range?v1{
??????if?k1?==?"evalTotalTime"{
???????switch?val?:=?v11.(type)?{
???????case?float64:
????????time?=?val
???????//?fmt.Println(time)
???????}
??????}
?????}
????}
???}
??}
??b?:=?&requestBody{
???query:?query,
???time:?time,
??}
??if?_,o?:=?result[request];o{
???b.count?=?result[request].count?+?1
???b.time?=?b.time?+?result[request].time
???result[request]?=?b
??}else?{
???b.count?=?1
???result[request]?=?b
??}
?}
}
統(tǒng)計(jì)好所有的請(qǐng)求次數(shù)與請(qǐng)求時(shí)間和后,我們還需要進(jìn)一步處理,得到每次請(qǐng)求的平均時(shí)間。
//analysis?data
func?analysisBody()??{
?for?k,v?:=?range?result{
??req?:=?&requestBody{}
??req.time?=?v.time?/?float64(v.count)
??req.count?=?v.count
??req.query?=?v.query
??analysis[k]?=?req
?}
}
分析好了日志后,下面我們開(kāi)始導(dǎo)出excel。
?倒出excel文件
這里使用的是excelize庫(kù)。首先進(jìn)行安裝:
go?get?github.com/360EntSecGroup-Skylar/excelize
excelize 詳細(xì)的文檔請(qǐng)點(diǎn)擊:https://xuri.me/excelize/zh-hans/。這里就不講解具體的使用方法了,直接上代碼了??梢酝扑]一個(gè)博客,我也是在這上面學(xué)習(xí)的。傳送門(mén)。這個(gè)庫(kù)還可以合并單元格,更多玩法,歡迎解鎖。
導(dǎo)出代碼示例如下:
type?cellValue?struct?{
?sheet?string
?cell?string
?value?string
}
//export?excel
func?exportExcel()??{
?file?:=?excelize.NewFile()
?//insert?title
?cellValues?:=?make([]*cellValue,0)
?cellValues?=?append(cellValues,&cellValue{
??sheet:?"sheet1",
??cell:?"A1",
??value:?"request",
?},&cellValue{
??sheet:?"sheet1",
??cell:?"B1",
??value:?"count",
?},&cellValue{
??sheet:?"sheet1",
??cell:?"C1",
??value:?"query",
?},&cellValue{
??sheet:?"sheet1",
??cell:?"D1",
??value:?"avgTime",
?})
?index?:=?file.NewSheet("Sheet1")
?//?設(shè)置工作簿的默認(rèn)工作表
?file.SetActiveSheet(index)
?for?_,?cellValue?:=?range?cellValues?{
??file.SetCellValue(cellValue.sheet,?cellValue.cell,?cellValue.value)
?}
?//insert?data
?cnt?:=?1
?for?k,v?:=?range?analysis{
??cnt?=?cnt?+?1
??for?k1,v1?:=?range?cellValues{
???switch?k1?{
???case?0:
????v1.cell?=?fmt.Sprintf("A%d",cnt)
????v1.value?=?k
???case?1:
????v1.cell?=?fmt.Sprintf("B%d",cnt)
????v1.value?=?fmt.Sprintf("%d",v.count)
???case?2:
????v1.cell?=?fmt.Sprintf("C%d",cnt)
????v1.value?=?v.query
???case?3:
????v1.cell?=?fmt.Sprintf("D%d",cnt)
????v1.value?=?strconv.FormatFloat(v.time,'f',-1,64)
???}
??}
??for?_,vc?:=?range?cellValues{
???file.SetCellValue(vc.sheet,vc.cell,vc.value)
??}
?}
?//generate?file
?err?:=?file.SaveAs("./log.xlsx")
?if?err?!=?nil{
??fmt.Errorf("generate?excel?error")
?}
}
#?4. 結(jié)果展示

怎么樣,還可以吧,我們可以看到請(qǐng)求次數(shù)與平均時(shí)間,一目了然。
我也是第一次使用go進(jìn)行日志分析。總體來(lái)說(shuō)還是挺方便的。最主要是導(dǎo)出excel真的很方便。你學(xué)會(huì)了嗎?
? ?

???
