zoukankan      html  css  js  c++  java
  • [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")
    
    

    最终效果:

    小结

  • 相关阅读:
    Linux 环境变量 设置 etc profile
    Linux 升级glibc-2.14 失败 我遇到的问题
    qt窗口的切换
    qt事件机制---事件范例
    qt中的事件机制
    qt的信号与槽函数
    linux下qt的安装
    qt中的udp编程
    qt中的tcp编程
    qt中的多线程
  • 原文地址:https://www.cnblogs.com/iiiiiher/p/8244145.html
Copyright © 2011-2022 走看看