[py]access日志入mysql-通过flask前端展示

pymysql组装sql入库日志

pymysql模块的用法

采集这些指标(metirc)都是linux环境,会用到mysql,做为数据的存储,我用docker来启动

docker run  
-p 3306:3306 
-v /data/mysql:/var/lib/mysql 
-v /etc/localtime:/etc/localtime 
--name mysql5 
--restart=always 
-e MYSQL_ROOT_PASSWORD=123456 
-d mysql:5.6.23 
--character-set-server=utf8 
--collation-server=utf8_general_ci
create database mem;
create table mem_used(used int, time int)


- 时间格式
>>> import time
>>> time.time()
1516860432.386474

- 入库后是整形,db自动处理, 也可以入库前int(time).
MySQL [mem]> select * from mem_used;
+-----------+------------+
| used      | time       |
+-----------+------------+
| 628658176 | 1516842082 |
| 628813824 | 1516842083 |
| 628936704 | 1516842084 |
| 628936704 | 1516842085 |
...


一个用法实例, 获取指标(通过psutil模块直接获取到已使用的内存) 也可以自己算出.

import psutil
import time
import pymysql as ms

con = ms.connect(host='127.0.0.1', user='root', passwd='123456', db='mem')
con.autocommit(True)
cur = con.cursor()

while True:
    used = psutil.virtual_memory().used
    sql = 'insert into mem_used values(%s,%s)' % (used / 1024 / 1024, int(time.time()))  ## Mb
    cur.execute(sql)
    time.sleep(1)


- %s可以接受任意类型的值

后面会将db操作封装成模块.

说下本次的任务

  • 本次的任务是将apache的access.log的(ip+状态)+出现次数, 入库到mysql里做分析. 分析网站访问的top10
  • 日志样式如下, 以空格分隔 arr[0] arr[8] 分别是ip和状态码
61.159.140.123 - - [23/Aug/2014:00:01:42 +0800] "GET /favicon.ico HTTP/1.1" 404  "-" "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36 LBBROWSER" "-"
  • 通过pymysql模块操作mysql数据库

  • 将结果放在res={}一个大的字典里. 核心统计思路如下. 如果字典无key,则value初始化为1; 如果字典有key,则 以key对应的value +1

- 为何是这种格式,取决于echarts数据格式所需. 通常是找到一个合适的图,对图中数据胸有程序,即可以看到它需要一些什么数据,在看下echarts代码,看下需要什么格式的数据, 然后后端组装, json.dumps后返回.

res[(ip, status)] = res.get((ip, status), 0) + 1
  • 对字典的值排序
- 遍历日志文件,获取到一个大的字典如下
res = {('182.145.101.123', '200'): 302, ('139.205.220.131', '404'): 1, ('119.146.75.13', '304'): 10, ('59.39.103.85', '200'): 56, ... }

- 对字典排序 转成 列表  后插入
print sorted(res.items(), key=lambda x: x[1], reverse=True)

[(('123.174.51.164', '200'), 6930), (('117.63.146.40', '200'), 1457), (('118.112.143.148', '404'), 1336), (('111.85.34.165', '200'), 1325), ...]


- 对排序后的列表(字典),字符串拼接sql,并插入数据库
for i in sorted(res.items(), key=iambda x: x[1], reverse=True):
    sql = "insert into iog vaiues ('%s','%s','%s')" % (i[0][0], i[0][1], i[1])
    db.execute(sql)

代码组织

入库代码

log2db.py

#!/usr/bin/env python
# coding=utf-8

from dbutils import DB

# 连接mysql
db = DB(
    host="127.0.0.1",
    user="root",
    passwd="",
    db="logtest"
)

# 日志处理成一个大的指定格式的字典(已统计好出现的次数) ((ip, status), count)
res = {}
with open('log.txt') as f:
    for line in f:
        if line == "
":
            continue
        arr = line.split(" ")
        ip = arr[0]
        status = arr[8]
        res[(ip, status)] = res.get((ip, status), 0) + 1

# 组合sql,执行sql入库日志
for l in sorted(res.items(), key=lambda x: x[1], reverse=True):
    # {('192.168.1.1',404): 1000,('192.168.1.1',403): 3000,('192.168.1.1',200): 2000,}
    sql = "insert into log values ('%s','%s','%s')" % (l[0][0], l[0][1], l[1])
    db.execute(sql)

dbutils.py

#!/usr/bin/env python
# coding=utf-8

import pymysql as ms


class DB:
    def __init__(self, host, user, passwd, db):
        self.host = host
        self.user = user
        self.passwd = passwd
        self.db = db
        self.connect()

    def connect(self):
        self.conn = ms.connect(
            host=self.host,
            user=self.user,
            passwd=self.passwd,
            db=self.db
        )
        self.conn.autocommit(True)
        self.cursor = self.conn.cursor()

    def execute(self, sql):
        try:
            self.cursor.execute(sql)
        except Exception as e:
            self.cursor.close()
            self.conn.close()
            self.connect()
            return self.execute(sql)
        else:
            return self.cursor

    # def query(self,tables):
    #     sql = 'select * from users'
    #     self.cursor.execute(sql)
    #     return self.cursor.fetchall()

下载实例日志: https://github.com/lannyMa/flask_info/blob/master/demo5/log.txt

执行脚本入库

python log2db.py

结果查看

入库完成后, flask前端展示+echarts

将入库的日志通过flask前端展示

难点是找图形的数据模型,即json数据格式. 后端返回. 所以当一幅图出来后,要对其上面有啥数据,啥数据是后端返回的,要胸有成竹, 通过代码+浏览器f12conson.log打印出来.

echarts官网上去看最简单实例的教程,用flask展示出来

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>ECharts</title>
    <!-- 引入 echarts.js -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/4.0.2/echarts.min.js"></script>
</head>
<body>
<!-- 为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style=" 600px;height:400px;"></div>
<script type="text/javascript">
    // 基于准备好的dom,初始化echarts实例
    var myChart = echarts.init(document.getElementById('main'));

    // 指定图表的配置项和数据
    var option = {
        title: {
            text: 'ECharts 入门示例'
        },
        tooltip: {},
        legend: {
            data: ['销量']
        },
        xAxis: {
            data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"]
        },
        yAxis: {},
        series: [{
            name: '销量',
            type: 'bar',
            data: [5, 20, 36, 10, 10, 20]
        }]
    };

    // 使用刚指定的配置项和数据显示图表。
    myChart.setOption(option);
</script>
</body>
</html>

echarts.min.js路径替换下

flask启动展示

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('demo.html')

if __name__ == '__main__':
    app.run(debug=True)

先找一个合适的echarts图,观察它所需的接口数据格式,后设计接口

计划使用echarts这个饼图展示

它的源码如下

option = {
    title : {
        text: '某站点用户访问来源',
        subtext: '纯属虚构',
        x:'center'
    },
    tooltip : {
        trigger: 'item',
        formatter: "{a} <br/>{b} : {c} ({d}%)"
    },
    legend: {
        orient: 'vertical',
        left: 'left',
        data: ['直接访问','邮件营销','联盟广告','视频广告','搜索引擎']
    },
    series : [
        {
            name: '访问来源',
            type: 'pie',
            radius : '55%',
            center: ['50%', '60%'],
            data:[
                {value:335, name:'直接访问'},
                {value:310, name:'邮件营销'},
                {value:234, name:'联盟广告'},
                {value:135, name:'视频广告'},
                {value:1548, name:'搜索引擎'}
            ],
            itemStyle: {
                emphasis: {
                    shadowBlur: 10,
                    shadowOffsetX: 0,
                    shadowColor: 'rgba(0, 0, 0, 0.5)'
                }
            }
        }
    ]
};

下载后,修改echarts-pie.html源码

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>ECharts</title>

    <!-- 引入 echarts.js -->
    <script src="/static/jquery.min.js"></script>
    <script src="/static/echarts.min.js"></script>
</head>
<body>

<!-- 为ECharts准备一个具备大小(宽高)的Dom画布 -->
<div id="main" style=" 600px;height:400px;"></div>

<script type="text/javascript">
    // 基于准备好的dom,初始化echarts实例
    var myChart = echarts.init(document.getElementById('main'));

    // 指定图表的配置项和数据
    var option = {
        title: {
            text: '某站点用户访问来源',
            {#            subtext: '纯属虚构',#}
            x: 'center'
        },
        tooltip: {
            trigger: 'item',
            formatter: "{a} <br/>{b} : {c} ({d}%)"
        },
        toolbox: {
            feature: {
                saveAsImage: {
                    show: true
                }
            }
        },
        legend: {
            orient: 'vertical',
            left: 'left',
            {#            data: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎']#}
        },
        series: [
            {
                name: '访问来源',
                type: 'pie',
                radius: '55%',
                center: ['50%', '60%'],
                {#                data: [#}
                {#                    {value: 335, name: '直接访问'},#}
                {#                    {value: 310, name: '邮件营销'},#}
                {#                    {value: 234, name: '联盟广告'},#}
                {#                    {value: 135, name: '视频广告'},#}
                {#                    {value: 1548, name: '搜索引擎'}#}
                {#                ],#}
                itemStyle: {
                    emphasis: {
                        shadowBlur: 10,
                        shadowOffsetX: 0,
                        shadowColor: 'rgba(0, 0, 0, 0.5)'
                    }
                }
            }
        ]
    };

    //使用jq去访问api获取得到数据.
    $.getJSON('/piedata', function (res) {
        option.legend.data = res.legend
        option.series[0].data = res.data

        // 使用刚指定的配置项和数据显示图表-绑定数据
        myChart.setOption(option);
    })
</script>
</body>
</html>

我们知道了前端js需要的数据所需要的数据格式后,就从后端构造这样的api,供前端调用

- api需返回数据的格式
{"data": [{"name": 200, "value": 49691}, {"name": 206, "value": 32}, {"name": 301, "value": 2}, {"name": 304, "value": 7584}, {"name": 403, "value": 1}, {"name": 404, "value": 3858}], "legend": [200, 206, 301, 304, 403, 404]}

- js访问接口方法
$.getJSON('/piedata', function (res) {
    option.legend.data = res.legend
    option.series[0].data = res.data
    myChart.setOption(option);  //获取后直接绑定
})

设计top10状态码的api

  • 目标是
即访问
http://127.0.0.1:5000/piedata

返回json数据:

{"data": [{"name": 200, "value": 49691}, {"name": 206, "value": 32}, {"name": 301, "value": 2}, {"name": 304, "value": 7584}, {"name": 403, "value": 1}, {"name": 404, "value": 3858}], "legend": [200, 206, 301, 304, 403, 404]}
  • 查库后得到的结果
- 用sql语句做数据汇聚,查出status 和 对应的总数, 按照状态码总数来排序
select status,sum(count) from log group by status;

查询结果如下

  • flask写api

- cur.fetchall返回的结果,需对其遍历,重组一定格式的数据
((200, Decimal('49691')), (206, Decimal('32')), (301, Decimal('2')), (304, Decimal('7584')), (403, Decimal('1')), (404, Decimal('3858')))


@app.route("/piedata")
def piedata():
    sql = "select status,sum(count) from log group by status";
    cur = db.execute(sql)
    # 构造前端所需的数据结构
    res = {
        'legend': [],
        'data': []
    }
    for c in cur.fetchall():
        code = c[0]
        count = int(c[1])
        res['legend'].append(code)
        res['data'].append({
            'name': code,
            'value': count
        })
    print(res)
    return json.dumps(res)


- 重组后数据格式如下
{'data': [{'name': 200, 'value': 49691}, {'name': 206, 'value': 32}, {'name': 301, 'value': 2}, {'name': 304, 'value': 7584}, {'name': 403, 'value': 1}, {'name': 404, 'value': 3858}], 'legend': [200, 206, 301, 304, 403, 404]}

- 通过json模块处理后返回给将这批数据返回给前端

最终访问

实现前端html展示

@app.route("/")
def index():
    return render_template("echarts-pie.html")

最终效果:

小结

原文地址:https://www.cnblogs.com/iiiiiher/p/8244145.html