shell + go + mysql nginx日志统计 (二) :统计及存入数据库

开始

首先把我们要采集的日志拿出来,大概这个样子

123.131.xx.xxx 307 0.012 [2018-01-16T10:42:50+08:00] POST /login HTTP/1.1 - 0 .......
121.19.xx.xx 200 0.010 [2018-01-16T10:42:51+08:00] GET / HTTP/1.1 - 4228 ........
120.221.xxx.xx 200 0.007 [2018-01-16T10:42:56+08:00] GET / HTTP/1.1 - 4227 .........

而我所接触的服务中一个服务大概每天产生90万条访问日志,而类似的服务有6个左右。其他一些林散的服务每个每天大概产生日志30-40万条左右。再来看看我的机器性能,4核8G带宽1M的一台机器,上面运行了zabbix,jenkins,mysql等程序,白天有日志查看需求的时候,带宽占用也比较大。

所以一次性运行完,而且还要让统计后的结果尽量的小,就成了需要思考的问题。不是收集所有日志而只是把相同的统计到一起,所以时间粒度也就需要放大一点,这里我统计的每个小时不同URL的访问时间,IP,状态码等。当然如果需要更加精确的统计数据比如说秒,分也是可以做的,这个放到后面再说。

为什么用go?

1.我刚好开始学go语言,才把语法弄清楚了一些。
2.看到一片讲词频统计的代码片段 地址https://studygolang.com/articles/3393 觉得这个刚好能解决我的问题就照着写了。
3.一次编译到处运行,这一点是我觉得最爽的地方

首先是引入需要用到的包

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
    "flag"
    "github.com/astaxie/beego/orm"
    _ "github.com/go-sql-driver/mysql"
    "strconv"
    "time"
)

之后定义数据的格式

\\用于Nginx响应时间
type ngx_res struct {
    Id        int64
    Date      time.Time
    Url       string
    Project   string
    Xiaoyu10  int
    Xiaoyu50  int
    Xiaoyu100 int
    Xiaoyu500 int
    Dayu500   int
}
\\用于IP访问次数
type ngx_ip struct {
    Id      int64
    Date    time.Time
    Project string
    Ip      string
    Times   string
}
\\用于状态码,Url,次数
type ngx_access struct {
    Id      int64
    Date    time.Time
    Project string
    Code    int64
    Url     string
    Times   int
}
type time_res struct {
    times_10       int
    times_50       int
    times_100      int
    times_500      int
    times_dayu_500 int
}

然后定义三个map 之后会把统计的东西放进去

var hourmap map[string]int = make(map[string]int, 0)
var resmap map[string]time_res = make(map[string]time_res, 0)
var ipmap map[string]int = make(map[string]int, 0)

定义一个读取及统计文件的函数

func read(filename string) {
//根据文件名读取文件
    fi, err := os.Open(filename)
    if err != nil {
        fmt.Printf("Error: %s\n", err)
        return
    }
    defer fi.Close()

    br := bufio.NewReader(fi)

    for {
//按照\n为分隔符来for循环
        a, err := br.ReadString('\n')
        if err != nil {
            break
        }
        log := string(a)

//这里就开始分日志了
        //这里可以看作再每行里面操作
        split := strings.Split(log, " ")
        ip := split[0]
        //取出日期
        date_tmp := strings.Split(split[3], "[")[1]
        date_string := strings.Split(date_tmp, ":")[0]
        //取出url,并且去掉=符号和?号后带的参数
        url1 := strings.Split(split[5], "?")[0]
        url := strings.Split(url1, "=")[0]
        //取出状态码
        code := split[1]
        //把日志中取出的响应时间转化微float64类型
        resp, err := strconv.ParseFloat(split[2], 64)
        if err != nil {
            break
        }
//这里开始就是写入到map中了
        //把  日期:url:状态码 作为键  访问次数作为值 传入hourmap中
        hourmap[date_string+":"+url+":"+code]++
        //把  日期:访问IP 作为键  访问次数作为值 传入ipmap中
        ipmap[date_string+":"+ip]++
        //把  日期:URL 作为键  把之前定义的time_res作为值 传入resmap中
        v, ok := resmap[date_string+":"+url]
        //判断,如果这个键存在就把resp拿出来做下面的判断在相应的地方加1,如果这个键不存在就再判断后创建这个键值对
        if ok {
            if resp <= 0.01 {
                a := time_res{v.times_10 + 1, v.times_50, v.times_100, v.times_500, v.times_dayu_500}
                resmap[date_string+":"+url] = a
            } else if resp > 0.01 && resp <= 0.05 {
                a := time_res{v.times_10, v.times_50 + 1, v.times_100, v.times_500, v.times_dayu_500}
                resmap[date_string+":"+url] = a
            } else if resp > 0.05 && resp <= 0.1 {
                a := time_res{v.times_10, v.times_50, v.times_100 + 1, v.times_500, v.times_dayu_500}
                resmap[date_string+":"+url] = a
            } else if resp > 0.1 && resp <= 0.5 {
                a := time_res{v.times_10, v.times_50, v.times_100, v.times_500 + 1, v.times_dayu_500}
                resmap[date_string+":"+url] = a
            } else {
                a := time_res{v.times_10, v.times_50, v.times_100, v.times_500, v.times_dayu_500 + 1}
                resmap[date_string+":"+url] = a
            }
        } else {
            if resp <= 0.01 {
                a := time_res{1, 0, 0, 0, 0}
                resmap[date_string+":"+url] = a
            } else if resp > 0.01 && resp <= 0.05 {
                a := time_res{0, 1, 0, 0, 0}
                resmap[date_string+":"+url] = a
            } else if resp > 0.1 && resp <= 0.5 {
                a := time_res{0, 0, 1, 0, 0}
                resmap[date_string+":"+url] = a
            } else if resp > 0.1 && resp <= 0.5 {
                a := time_res{0, 0, 0, 1, 0}
                resmap[date_string+":"+url] = a
            } else {
                a := time_res{0, 0, 0, 0, 1}
                resmap[date_string+":"+url] = a
            }
        }
    }
}

写入数据库

需要在这里说下的是如果你是统计的完全不相干的项目的日志,我认为不放在一个表里面是比较好的,也就是修改一下上面的数据格式名称,再下面初始化数据库的时候再修改new()中的东西再在后面改下sql中的表名。

这里写入数据库我使用beego提供的orm,事实上我只会着一种方式 。选用的数据库是mariadb.这里有个坑,mariadb的timezone CST 是美国中部时间。。。。。

初始化数据库

func RegisterDb(uname string, passwd string, ipaddr string, port string, databasename string) {
    orm.RegisterDriver("mysql", orm.DRMySQL)
    orm.RegisterDataBase("default", "mysql", uname+":"+passwd+"@tcp("+ipaddr+":"+port+")/"+databasename+"?charset=utf8", 10)
    orm.RegisterModel(new(ngx_access), new(ngx_ip), new(ngx_res))
}

定义插入数据的函数

func Add_access(project string, date string, code string, url string, times int) error {
    o := orm.NewOrm()

    codes, err := strconv.ParseInt(code, 10, 64)
    if err != nil {
        return err
    }
    _, error := o.Raw("INSERT INTO `ngx_access` (`date`, `project`, `code`, `url`, `times`) VALUES (?, ?, ?, ?, ?);", date, project, codes, url, times).Exec()
    return error
}
func Add_ip(project string, date string, ip string, times int) error {
    o := orm.NewOrm()

    _, error := o.Raw("INSERT INTO `ngx_ip` (`date`, `project`, `ip`,`times`) VALUES (?, ?, ?, ?);", date, project, ip, times).Exec()
    return error
}
func Add_res(project string, date string, url string, xiaoyu10 int, xiaoyu50 int, xiaoyu100 int, xiaoyu500 int, dayu500 int) error {
    o := orm.NewOrm()

    _, error := o.Raw("INSERT INTO `ngx_res` (`date`, `project`,`url`,`xiaoyu10`,`xiaoyu50`,`xiaoyu100`,`xiaoyu500`,`dayu500`) VALUES (?, ?, ?, ?, ?, ?, ?, ?);", date, project, url, xiaoyu10, xiaoyu50, xiaoyu100, xiaoyu500, dayu500).Exec()
    return error
}

再定义一个时间替换函数,作用是把字符串转换为时间类型

func time_tihuan(date_hour string) time.Time {
    //输入时间字符串并拼接
    //time_string := date_hour
    //获取服务器时区
    //loc, _ := time.LoadLocation("Asia/Chongqing")

    //字符串转为时间类型
    theTime, err := time.Parse("2006-01-02T15:04:05 -0700", date_hour)
    if err != nil {
        fmt.Println(err)
    }
    return theTime
}

初始化数据库填入数据库的连接信息

func init() {
    RegisterDb("uername", "password", "xxx.xxx.xxx.xxx", "xxxx", "databasename")
}

主函数定义

func main() {
//定义一个从命令行传入参数函数把filename从命令行传入
    var filename string
    flag.StringVar(&filename, "filename", "2017-12-35_xxxxx.log", "nginx access log filename!")
    flag.Parse()
    //read函数 执行后数据统计入map中
    read(filename)
    orm.Debug = true
    orm.RunSyncdb("default", false, true)
//更具filename 来确定project 的名字
    project1 := strings.Split(filename, ".")[0]
    project := strings.Split(project1, "_")[1]
//定义一个map 用来存放一小时只有一次访问的URL,用于去除类似扫描器之类的无效访问。
    var hourmap_one map[string]int = make(map[string]int, 0)
    for k, v := range hourmap {
         //hourmap如果键的值不等于1则写入数据库,反之写入hourmap_one
        if v != 1 {
            a := strings.Split(k, ":")
            date := time_tihuan(a[0] + ":00:00 +0800").Format("2006-01-02 15:04:05 -0700")
            Add_access(project, date, a[2], a[1], v)
        } else {
            a := strings.Split(k, ":")
            hourmap_one[a[0]+":oneurl:200"]++
        }
    }
//把hourmap_one写入数据库
    for k, v := range hourmap_one {
        a := strings.Split(k, ":")
        date := time_tihuan(a[0] + ":00:00 +0800").Format("2006-01-02 15:04:05 -0700")
        Add_access(project, date, a[2], a[1], v)
    }
//把ipmap每小时大于5次访问的IP写入数据库
    for k, v := range ipmap {
        if v > 5 {
            a := strings.Split(k, ":")
            date := time_tihuan(a[0] + ":00:00 +0800").Format("2006-01-02 15:04:05 -0700")
            Add_ip(project, date, a[1], v)
        }
    }
//把resmap写入数据库
    for k, v := range resmap {

        a := strings.Split(k, ":")
        date := time_tihuan(a[0] + ":00:00 +0800").Format("2006-01-02 15:04:05 -0700")
        Add_res(project, date, a[1], v.times_10, v.times_50, v.times_100, v.times_500, v.times_dayu_500)
    }

}

完整的代码

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
    //"sort"
    "flag"
    "github.com/astaxie/beego/orm"
    _ "github.com/go-sql-driver/mysql"
    "strconv"
    "time"
)

//写入数据库
type ngx_res struct {
    Id        int64
    Date      time.Time
    Url       string
    Project   string
    Xiaoyu10  int
    Xiaoyu50  int
    Xiaoyu100 int
    Xiaoyu500 int
    Dayu500   int
}
type ngx_ip struct {
    Id      int64
    Date    time.Time
    Project string
    Ip      string
    Times   string
}
type ngx_access struct {
    Id      int64
    Date    time.Time
    Project string
    Code    int64
    Url     string
    Times   int
}
type time_res struct {
    times_10       int
    times_50       int
    times_100      int
    times_500      int
    times_dayu_500 int
}

func Add_access(project string, date string, code string, url string, times int) error {
    o := orm.NewOrm()

    codes, err := strconv.ParseInt(code, 10, 64)
    if err != nil {
        return err
    }
    _, error := o.Raw("INSERT INTO `ngx_access` (`date`, `project`, `code`, `url`, `times`) VALUES (?, ?, ?, ?, ?);", date, project, codes, url, times).Exec()
    return error
}
func Add_ip(project string, date string, ip string, times int) error {
    o := orm.NewOrm()

    _, error := o.Raw("INSERT INTO `ngx_ip` (`date`, `project`, `ip`,`times`) VALUES (?, ?, ?, ?);", date, project, ip, times).Exec()
    return error
}
func Add_res(project string, date string, url string, xiaoyu10 int, xiaoyu50 int, xiaoyu100 int, xiaoyu500 int, dayu500 int) error {
    o := orm.NewOrm()

    _, error := o.Raw("INSERT INTO `ngx_res` (`date`, `project`,`url`,`xiaoyu10`,`xiaoyu50`,`xiaoyu100`,`xiaoyu500`,`dayu500`) VALUES (?, ?, ?, ?, ?, ?, ?, ?);", date, project, url, xiaoyu10, xiaoyu50, xiaoyu100, xiaoyu500, dayu500).Exec()
    return error
}

//初始化数据库
func RegisterDb(uname string, passwd string, ipaddr string, port string, databasename string) {
    orm.RegisterDriver("mysql", orm.DRMySQL)
    orm.RegisterDataBase("default", "mysql", uname+":"+passwd+"@tcp("+ipaddr+":"+port+")/"+databasename+"?charset=utf8", 10)
    orm.RegisterModel(new(ngx_access), new(ngx_ip), new(ngx_res))
}

var hourmap map[string]int = make(map[string]int, 0)
var resmap map[string]time_res = make(map[string]time_res, 0)
var ipmap map[string]int = make(map[string]int, 0)

//读取文件
func read(filename string) {
    fi, err := os.Open(filename)
    if err != nil {
        fmt.Printf("Error: %s\n", err)
        return
    }
    defer fi.Close()

    br := bufio.NewReader(fi)
    for {
        a, err := br.ReadString('\n')
        if err != nil {
            break
        }
        log := string(a)
        //计算每小时访问次数
        split := strings.Split(log, " ")
        ip := split[0]
        date_tmp := strings.Split(split[3], "[")[1]
        date_string := strings.Split(date_tmp, ":")[0]
        //      date_time := time_tihuan(date_string[0],date_string[1])
        url1 := strings.Split(split[5], "?")[0]
        url := strings.Split(url1, "=")[0]
        code := split[1]
        resp, err := strconv.ParseFloat(split[2], 64)
        if err != nil {
            break
        }
        hourmap[date_string+":"+url+":"+code]++
        ipmap[date_string+":"+ip]++
        v, ok := resmap[date_string+":"+url]
        if ok {
            if resp <= 0.01 {
                a := time_res{v.times_10 + 1, v.times_50, v.times_100, v.times_500, v.times_dayu_500}
                resmap[date_string+":"+url] = a
            } else if resp > 0.01 && resp <= 0.05 {
                a := time_res{v.times_10, v.times_50 + 1, v.times_100, v.times_500, v.times_dayu_500}
                resmap[date_string+":"+url] = a
            } else if resp > 0.05 && resp <= 0.1 {
                a := time_res{v.times_10, v.times_50, v.times_100 + 1, v.times_500, v.times_dayu_500}
                resmap[date_string+":"+url] = a
            } else if resp > 0.1 && resp <= 0.5 {
                a := time_res{v.times_10, v.times_50, v.times_100, v.times_500 + 1, v.times_dayu_500}
                resmap[date_string+":"+url] = a
            } else {
                a := time_res{v.times_10, v.times_50, v.times_100, v.times_500, v.times_dayu_500 + 1}
                resmap[date_string+":"+url] = a
            }
        } else {
            if resp <= 0.01 {
                a := time_res{1, 0, 0, 0, 0}
                resmap[date_string+":"+url] = a
            } else if resp > 0.01 && resp <= 0.05 {
                a := time_res{0, 1, 0, 0, 0}
                resmap[date_string+":"+url] = a
            } else if resp > 0.1 && resp <= 0.5 {
                a := time_res{0, 0, 1, 0, 0}
                resmap[date_string+":"+url] = a
            } else if resp > 0.1 && resp <= 0.5 {
                a := time_res{0, 0, 0, 1, 0}
                resmap[date_string+":"+url] = a
            } else {
                a := time_res{0, 0, 0, 0, 1}
                resmap[date_string+":"+url] = a
            }
        }
    }
}

//时间转换函数
func time_tihuan(date_hour string) time.Time {
    //输入时间字符串并拼接
    //time_string := date_hour
    //获取服务器时区
    //loc, _ := time.LoadLocation("Asia/Chongqing")

    //字符串转为时间类型
    theTime, err := time.Parse("2006-01-02T15:04:05 -0700", date_hour)
    if err != nil {
        fmt.Println(err)
    }
    return theTime
}

func init() {
    RegisterDb("username", "password", "ipaddr", "port", "databasename")
}
func main() {
    var filename string
    flag.StringVar(&filename, "filename", "2017-12-35_mobile.log", "nginx access log filename!")
    flag.Parse()
    //read函数 执行后数据统计入map中
    read(filename)
    orm.Debug = true
    orm.RunSyncdb("default", false, true)
    project1 := strings.Split(filename, ".")[0]
    project := strings.Split(project1, "_")[1]
    var hourmap_one map[string]int = make(map[string]int, 0)
    for k, v := range hourmap {
        if v != 1 {
            a := strings.Split(k, ":")
            date := time_tihuan(a[0] + ":00:00 +0800").Format("2006-01-02 15:04:05 -0700")
            Add_access(project, date, a[2], a[1], v)
        } else {
            a := strings.Split(k, ":")
            hourmap_one[a[0]+":oneurl:200"]++
        }
    }
    for k, v := range hourmap_one {
        a := strings.Split(k, ":")
        date := time_tihuan(a[0] + ":00:00 +0800").Format("2006-01-02 15:04:05 -0700")
        Add_access(project, date, a[2], a[1], v)
    }
    for k, v := range ipmap {
        if v > 5 {
            a := strings.Split(k, ":")
            date := time_tihuan(a[0] + ":00:00 +0800").Format("2006-01-02 15:04:05 -0700")
            Add_ip(project, date, a[1], v)
        }
    }
    for k, v := range resmap {

        a := strings.Split(k, ":")
        date := time_tihuan(a[0] + ":00:00 +0800").Format("2006-01-02 15:04:05 -0700")
        Add_res(project, date, a[1], v.times_10, v.times_50, v.times_100, v.times_500, v.times_dayu_500)
    }

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容

  • 需要原文的可以留下邮箱我给你发,这里的文章少了很多图,懒得网上粘啦 1数据库基础 1.1数据库定义 1)数据库(D...
    极简纯粹_阅读 7,393评论 0 46
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,359评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,579评论 18 139
  • 天渐渐要黑下来了,高原的天气,昼夜温差很大,已经冻得瑟瑟发抖了,还想欣赏一下夕阳美。 顺着这条小河...
    香风飘玉蕙阅读 323评论 10 10