[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 == "\n":
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="width: 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>
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图,观察它所需的接口数据格式,后设计接口
它的源码如下
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="width: 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")
最终效果: