安全法要求日志保存180天,很多网络设备基本都没法达到这个要求,但是都可以发送syslog,为了保存各个设备的日志文件,就手撸了一个日志服务器。
1、本日志服务器用于接收RFC3164格式的日志,分设备放置于不同文件夹,分日期进行切片存放,一个切片日志6M。有其他需要可以自己调整重新编译。
2、日志服务器默认使用udp 514端口,请在防火墙上设置允许通过。
3、日志服务器会在每天凌晨12点打包上一日的日志,方便进行存储。Windows下可能存在权限问题,右键属性设置一下即可。
PS:
1、该日志服务器解析RFC3164格式的日志,如果需要支持其他格式的日志,可以在源码中调整,可以支持RFC5424和RFC6587。
2、golang写入性能还不错,所以未添加写入缓存,如有需要,可以自行添加。
源码给出 大家可以自己编译。
main.go
[Golang] 纯文本查看 复制代码package mainimport ( "fmt" "io" "os" "runtime" "strconv" "strings" "time" "gopkg.in/mcuadros/go-syslog.v2")var fg stringvar hg stringvar fdate stringvar loghostf map[string]int //键值对,记录主机和日志切片数据,重新启动程序会自动刷新var logfilemax = 6 * 1024 * 1024 //6M一个分片var dt stringvar ot string //上一日var Logbase = "logs"func main() { systype := runtime.GOOS if systype == "windows" { fg = "\\" hg = "\r\n" } else { fg = "/" hg = "\n" } //dt = "2021-09-13" //测试数据,正式使用请删除 dt = time.Now().Format("2006-01-02") loghostf = make(map[string]int) //计数器 _, err := os.Stat(Logbase) if os.IsNotExist(err) { os.Mkdir(Logbase, os.ModePerm) //建立日志存放文件夹 } channel := make(syslog.LogPartsChannel) handler := syslog.NewChannelHandler(channel) server := syslog.NewServer() //建立syslog服务器 server.SetFormat(syslog.RFC3164) //日志格式 server.SetHandler(handler) server.ListenUDP("0.0.0.0:514") server.Boot() go func(channel syslog.LogPartsChannel) { for logParts := range channel { nt := time.Now().Format("2006-01-02") //日期发生变化时更新日期常数 if strings.Compare(dt, nt) != 0 { ot = dt dt = nt if loghostf != nil { //如果日期更新就初始化计数器 for k := range loghostf { delete(loghostf, k) } } go Dozipfordel(ot) } doinfile(logParts["hostname"].(string), logParts["content"].(string)) fmt.Println(logParts) } }(channel) server.Wait()}func doinfile(fhost string, fmsg string) { //写入日志文件 fpath := logfilestat(fhost) f, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModePerm) _, err = io.WriteString(f, fmsg+hg) if err != nil { fmt.Println("写入文件错误,请检查logs保存路径!") }}func logfilestat(fhostf string) string { //判断日志文件状态并进行切片 fnum, fa := loghostf[fhostf] if fa == false { //判断键值是否存在 _, err1 := os.Stat("logs" + fg + fhostf) if os.IsNotExist(err1) { os.MkdirAll("logs"+fg+fhostf, os.ModePerm) //新的hostname就新建对应的文件夹 } loghostf[fhostf] = 0 //第一次初始化写入键值 } fp := "logs" + fg + fhostf + fg + dt + "-" + strconv.Itoa(fnum) + ".log" ff, err2 := os.Stat(fp) //获取文件状态 if err2 == nil && fnum == 0 && ff.Size() > int64(logfilemax) { //重新启动时找到正确的切片值 for fexist(fp) { fnum = fnum + 1 fp = "logs" + fg + fhostf + fg + dt + "-" + strconv.Itoa(fnum) + ".log" } loghostf[fhostf] = fnum //fp = "logs" + fg + fhostf + fg + dt + "-" + strconv.Itoa(fnum) + ".log" } else { if err2 == nil && ff.Size() > int64(logfilemax) { //超过分片大小就新生成一个子文件 fnum = fnum + 1 loghostf[fhostf] = fnum fp = "logs" + fg + fhostf + fg + dt + "-" + strconv.Itoa(fnum) + ".log" } } return fp}func fexist(fp string) bool { //判断文件是否存在 ff, err := os.Stat(fp) if os.IsNotExist(err) || ff.Size() < int64(logfilemax) { //序列号不存在或该序列号对应的文件小于分割文件大小 return false } else { return true }}
zip.go
[Asm] 纯文本查看 复制代码package mainimport ( "archive/zip" "io" "io/ioutil" "os" "path/filepath" "strings")/**@files:需要压缩的文件@compreFile:压缩之后的文件*/func Compress_zip(files []*os.File, compreFile *os.File) (err error) { zw := zip.NewWriter(compreFile) defer zw.Close() for _, file := range files { err := compress_zip(file, zw) if err != nil { return err } file.Close() } return nil}/**功能:压缩文件@file:压缩文件@prefix:压缩文件内部的路径@tw:写入压缩文件的流*/func compress_zip(file *os.File, zw *zip.Writer) error { info, err := file.Stat() if err != nil { //logs.Error("压缩文件失败:", err.Error()) return err } // 获取压缩头信息 head, err := zip.FileInfoHeader(info) if err != nil { //logs.Error("压缩文件失败:", err.Error()) return err } // 指定文件压缩方式 默认为 Store 方式 该方式不压缩文件 只是转换为zip保存 head.Method = zip.Deflate fw, err := zw.CreateHeader(head) if err != nil { //logs.Error("压缩文件失败:", err.Error()) return err } // 写入文件到压缩包中 _, err = io.Copy(fw, file) file.Close() if err != nil { //logs.Error("压缩文件失败:", err.Error()) return err } return nil}/*获取需要压缩的子文件夹列表*/func getDirList(dirpath string) ([]string, error) { var dir_list []string dirinfo, err := ioutil.ReadDir(dirpath) if err == nil { for _, dinfo := range dirinfo { if dinfo.IsDir() { dir_list = append(dir_list, dinfo.Name()) //写入文件夹列表 } } } return dir_list, err}/*对上一日的日志文件进行压缩后删除文件*/func Dozipfordel(ot string) { //上一日日期,进行日志文件压缩 dps, err := getDirList(Logbase) if err == nil { for _, dpt := range dps { var zfiles []*os.File var filesname []string err = filepath.Walk(filepath.Join(Logbase, dpt), //便利log子目录下的所有文件,找到对应日期的文件 func(path string, f os.FileInfo, err error) error { if f == nil { return err } if strings.Contains(f.Name(), ot) { fn := filepath.Join(Logbase, dpt, f.Name()) //获取文件名,添加到文件名队列,用来后面的删除,同时在打开文件句柄 filesname = append(filesname, fn) of, _ := os.Open(fn) //defer of.Close() zfiles = append(zfiles, of) } return nil }) if len(zfiles) != 0 { //如果文件列表中 nf, err := os.OpenFile(filepath.Join(Logbase, dpt, ot)+".zip", os.O_WRONLY|os.O_CREATE, os.ModePerm) //defer nf.Close() if err == nil { if Compress_zip(zfiles, nf) == nil { //压缩成功后删除源文件 for _, fi := range filesname { os.Remove(fi) } } } } } }}
然后再放一个Windows下的编译好的文件,360可能有误报,自己判断。
温馨提示如有转载或引用以上内容之必要,敬请将
本文链接作为出处标注,谢谢合作!
发表评论: