golang實戰(zhàn)之truncate日志文件詳解
前言
有時候我們生成的日志文件很大,如果文件過大,或者磁盤空間有限,那么我們就需要把日志文件改小一點。
(當然實際應用場景中,一般采用log rotate的方式實現(xiàn)多個日志文件,定期把舊的日志文件挪走或者刪除)
現(xiàn)在我們考慮如何把日志文件變小的場景。
首先想到的是文件truncate,遺憾的是truncate是把新的內(nèi)容刪掉,而不是把舊的內(nèi)容刪掉;因為通常我們的做法是要把舊的日志內(nèi)容刪除,而保留最新的日志內(nèi)容。
方法1:拷貝文件,然后把前半部分刪除,保留新的后半部分
$ wc -l logfile
$ tail -n {LINENUM}/2 logfile > logfile2
$ mv logfile2 logfile
缺點是需要額外的臨時磁盤空間(logfile2),保留新拷貝的文件
方法2:把日志文件讀入內(nèi)存,截去前半部分,把后半部分重新寫會文件。
缺點也是要創(chuàng)建一個臨時文件,或者把整個文件內(nèi)容一次性讀入內(nèi)存。
下面是一個實現(xiàn)創(chuàng)建臨時文件的例子:
(如果采用整個文件讀入內(nèi)存,那么可以重復覆蓋使用原來的讀入文件,不用創(chuàng)建臨時文件):
package main
import (
"bufio"
"log"
"io"
"os"
)
func ReadLine(r *bufio.Reader, w *bufio.Writer) error {
for i := 0; ; i++ {
line, err := r.ReadString('\n')
if i >= 12554364/2 { // this number is hard-codes just for example use
if _,err := w.WriteString(line); err != nil {
log.Fatal(err)
}
}
if err != nil {
if err == io.EOF {
return nil
}
return err
}
}
return nil
}
func main() {
fin, err := os.Open("data.in")
if err != nil {
log.Fatal(err)
}
defer fin.Close()
fout, err := os.Create("data.dat")
if err != nil {
log.Fatal(err)
}
defer fout.Close()
reader := bufio.NewReader(fin)
writer := bufio.NewWriter(fout)
if err := ReadLine(reader, writer); err != nil {
log.Fatal(err)
}
writer.Flush()
}
運行:
對于1G大小的日志文件,在我的Linux上執(zhí)行時間大約是:
$ go build && time ./main real 0m6.972s user 0m4.668s sys 0m2.055s
方法3:也是利用文件,把文件mmap映射到內(nèi)存,然后使用copy函數(shù)覆蓋內(nèi)存。
缺點是,mmap操作日志文件不能過大。
下面是一個實現(xiàn)的代碼例子:
package main
import (
"fmt"
"log"
"os"
"syscall"
)
// Truncate file to half size, with line aligned.
func truncateText(f *os.File) error {
fi, err := f.Stat()
if err != nil {
return err
}
mem, err := syscall.Mmap(int(f.Fd()), 0, int(fi.Size()), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
if err != nil {
return err
}
size := fi.Size()
if size <= 1 {
// Don't need to truncate file if it's too small
return nil
}
trun := size/2 - 1
for ; trun < fi.Size(); trun ++ {
if mem[trun] == '\n' {
break
}
}
//fmt.Printf("size=%d, trun=%d\n", size, trun)
if trun >= size - 1 {
trun = size/2
} else {
trun = trun + 1
}
// Overwrite file content
copy(mem[0:], mem[trun:])
err = syscall.Munmap(mem)
if err != nil {
return err
}
// truncate file
f.Truncate(fi.Size() - trun)
// reset file offset
f.Seek(trun,0)
return nil
}
func main() {
path := "data.dat"
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
fmt.Println("Cannot create file")
log.Fatal(err)
}
err = truncateText(f)
if err != nil {
fmt.Println("Cannot truncateLog file")
log.Fatal(err)
}
f.Write([]byte("ABCD"))
f.Close()
}
函數(shù)truncateText的功能就是把文件截取到原來的一半大小,并保持按行對齊;分如下幾步:
- 先把文件映射syscall.Mmap 到內(nèi)存。
- 查找到中間行的位置。
- 然后用內(nèi)置函數(shù)copy,把中間行以后的挪動到文件開始位置。
- 修改文件的大小file.Truncate
- 重置文件的偏移指針offset
- 取消文件映射syscall.Munmap
運行:
對于1G大小的日志文件,在我的Linux上執(zhí)行時間大約是:
$ go build && time ./main real 0m1.861s user 0m0.335s sys 0m0.650s
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
Go在GoLand中引用github.com中的第三方包具體步驟
這篇文章主要給大家介紹了關(guān)于Go在GoLand中引用github.com中第三方包的具體步驟,文中通過圖文介紹的非常詳細,對大家學習或者使用Go具有一定的參考價值,需要的朋友可以參考下2024-01-01
jenkins配置golang?代碼工程自動發(fā)布的實現(xiàn)方法
這篇文章主要介紹了jenkins配置golang?代碼工程自動發(fā)布,jks是個很好的工具,使用方法也很多,我只用了它簡單的功能,對jenkins配置golang相關(guān)知識感興趣的朋友一起看看吧2022-07-07
golang利用unsafe操作未導出變量-Pointer使用詳解
這篇文章主要給大家介紹了關(guān)于golang利用unsafe操作未導出變量-Pointer使用的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧2018-08-08

