syslog小型服务器GO语言源代码

安全法要求日志保存180天,很多网络设备基本都没法达到这个要求,但是都可以发送syslog,为了保存各个设备的日志文件,小牛云站长这里就写了一个日志服务器。
功能作用:
1.本日志服务器用于接收RFC3164格式的日志,分设备放置于不同文件夹,分日期进行切片存放,一个切片日志6M。有其他需要可以自己调整重新编译。
2.日志服务器默认使用udp 514端口,请在防火墙上设置允许通过。
3.日志服务器会在每天凌晨12点打包上一日的日志,方便进行存储。Windows下可能存在权限问题,右键属性设置一下即可。
注意:
1.该日志服务器解析RFC3164格式的日志,如果需要支持其他格式的日志,可以在源码中调整,可以支持RFC5424和RFC6587。
2.golang写入性能还不错,所以未添加写入缓存,如有需要,可以自行添加。
源代码:
main.go
package main
 
import (
        "fmt"
        "io"
        "os"
        "runtime"
        "strconv"
        "strings"
        "time"
 
        "gopkg.in/mcuadros/go-syslog.v2"
)
 
var fg string
var hg string
var fdate string
var loghostf map[string]int      //键值对,记录主机和日志切片数据,重新启动程序会自动刷新
var logfilemax = 6 * 1024 * 1024 //6M一个分片
var dt string
var 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
package main
 
import (
        "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可能有误报,介意误下,直接用上面的代码编译即可。

 XnSay
 简介:热爱网络技术的一名草根站长-XnSay

  您阅读这篇文章共花了: 

发表评论

游客
送你一朵小花花~

帅人已评(0)