python项目代码分析_python项目日志分析

sg_trans.gif

日志分析

业务中会生成大量的系统日志、应用程序日志、安全日志等,通过对日志的分析可以了解服务器的负载、健康状况,可以分析客户的分布情况、客户的行为,甚至基于这些分析可以做出预测

一般采集流程

日志产出->采集(Logstash、Flume、Scribe)->存储->分析->存储(数据库、NoSQL)->可视化

开源实时日志分析ELK平台

Logstash收集日志,并存放到ElasticSearch集群中,Kibana则从ES集群中查询数据生成图表,返回浏览器端

分析日志à正则表达式提取数据à异常处理(抛异常或者返回特殊值None)

数据载入à窗口函数à数据分发(实现分发器)

(读文件) (时间窗口:数据求值,丢掉过期数据) (多线程,队列queue)

à文件加载及分析器实现

(整合前面功能) (状态码分析)

分析的前提

半结构化数据

日志是半结构化数据,是有组织的,有格式的数据。可以分割成行和列,就可以当作表理解和处理,分析里面的数据

文本分析

日志是文本文件,需要依赖文件IO、字符串操作、正则表达式等技术

通过这些技术就能够把日志中需要的数据提取出来

目标数据形如:

line ='''220.181.108.107

- - [18/Apr/2017:01:37:39

+0800]\"GET

/ HTTP/1.1" 200 8416 "-"\"Mozilla/5.0

(compatible; Baiduspider/2.0;

+http://www.baidu.com/search/spider.html)"'''

nginx、tomcat等WEB

Server都会产生这样的日志

提取数据

一、空格分割

importdatetime

defmakekey(line:str,chars=set('''

[]'"''')):

start

=0length

=len(line)

flag

=False#假设没有碰到开关fori,cinenumerate(line):ifcinchars:ifc

=='[':#开始,直到右括号start

= i +1#跳过[flag

=True

elifc

==']':

flag

=False#结束elifc

=='"':

flag

=notflag#双引号ifflag:#第一次碰到双引号start

= i +1ifflag:continue

ifstart

== i:

start

= i +1continue

yieldline[start:i]

start

= i +1else:ifstart

< length:yieldline[start:]

line ='''220.181.108.107

- - [18/Apr/2017:01:37:39

+0800]\"GET

/ HTTP/1.1" 200 8416 "-"\"Mozilla/5.0

(compatible; Baiduspider/2.0;

+http://www.baidu.com/search/spider.html)"'''names

= ('remote','','','datetime','request','status','size','','useragent')

ops = (None,

None,

None,#时间类型转换lambdadstr:

datetime.datetime.strptime(dstr,'%d/%b/%Y:%H:%M:%S

%z'),

lambdarequest:dict(zip(['method','url','protocol'],request.split())),#添加字段名int,int,

None, None) defextract(line:str):returndict(map(#映射字段名lambdatriple:

(triple[0],triple[1](triple[2])iftriple[1]elsetriple[2]),zip(names,ops,makekey(line))

)

) print(extract(line))#

print(list(makekey(line)))

二、正则表达式提取

importdatetimeimportre

line ='''220.181.108.107

- - [18/Apr/2017:01:37:39

+0800]\"GET

/ HTTP/1.1" 200 8416 "-"\"Mozilla/5.0

(compatible; Baiduspider/2.0;

+http://www.baidu.com/search/spider.html)"'''

ops

= {'datetime':lambdatimestr:

datetime.datetime.strptime(timestr,'%d/%b/%Y:%H:%M:%S

%z'),'status':int,'length':int}

pattern ='''(?P[\d.]{7,})

- - \[(?P[/\w +:]+)\]\"(?P\w+)

(?P\S+) (?P[\w/\d.]+)"\(?P\d+)

(?P\d+) .+ "(?P.+)"'''regex

= re.compile(pattern)defextract(line:str)

->dict:

matcher

= regex.match(line)return{k:

ops.get(k,

lambdax:x)(v)fork,vinmatcher.groupdict().items()}print(extract(line))

异常处理

日志中不免会出现一些不匹配的行,需要处理

这里使用re.match方法,有可能匹配不上。所以要增加一个判断

采用抛出异常的方式,让调用者获得异常并自行处理

defextract(line:str)

->dict:"""返回字段的字典,抛出异常说明匹配失败"""matcher

= regex.match(line)ifmatcher:return{k:

ops.get(k,

lambdax:

x)(v)fork,vinmatcher.groupdict().items()}else:raiseException('No

match. {}'.format(line))#或输出日志记录

或者返回一个特殊值,告知调用者没有匹配

defextract(line:str)

->dict:"""返回字段的字典,如果返回None说明匹配失败"""matcher

= regex.match(line)ifmatcher:return{k:

ops.get(k,

lambdax:

x)(v)fork,vinmatcher.groupdict().items()}else:raise

None#或输出日志记录

滑动窗口

数据载入

defload(path):"""装载日志文件"""withopen(path)asf:forlineinf:

fields

= extract(line)iffields:yieldfieldselse:continue#TODO解析失败就抛弃,或者打印日志

时间窗口分析

概念

很多数据,例如日志,都是和时间相关的,都是按照时间顺序产生的

产生的数据分析的时候,要按照时间求值

interval表示每一次求值的时间间隔

width时间窗接口宽度,指一次求值的时间窗口宽度

当width

> interval

blog_18a1100b10102yhwi.html

数据求值时会有重叠

当width

= interval

blog_18a1100b10102yhwi.html

数据求值没有重叠

时序数据

运维环境中,日志、监控等产生的数据都是与时间相关的数据,按照时间先后产生并记录下来的数据,所以一般按照时间对数据进行分析

数据分析基本程序结构

无限的生成随机数函数,产生时间相关的数据,返回时间和随机数字典

每次取3个数据,求平均值

importrandomimportdatetimeimporttime

defsource():while

True:yield{'value':

random.randint(1,100),'datetime':datetime.datetime.now()}

time.sleep(1)#攒一批数据s

= source()

items = [next(s)for_inrange(3)]#处理函数,送入一批数据计算一个结果defhandler(iterable):returnsum(map(lambdaitem:

item['value'],iterable))

/len(iterable)print(items)print("{:2f}".format(handler(items)))

窗口函数实现

将上面的获取数据的程序扩展为window函数,使用重叠的方案

importrandomimportdatetimeimporttimedefsource(second=1):"""生成数据"""while

True:yield{'value':

random.randint(1,100),'datetime':

datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=8)))}

time.sleep(second)defwindow(iterator,handler,width:int,interval:int):"""窗口函数:paramiterator:数据源,生成器,用来拿数据:paramhandler:数据处理函数:paramwidth:时间窗口宽度,秒:paraminterval:处理时间间隔,秒"""start

= datetime.datetime.strptime('20170101

000000 +0800','%Y%m%d

%H%M%S %z')

current

= datetime.datetime.strptime('20170101

010000 +0800','%Y%m%d

%H%M%S %z')

buffer

= []#窗口的待计算数据delta

= datetime.timedelta(seconds=width

- interval)while

True:#从数据源获取数据data

=next(iterator)ifdata:

buffer.append(data)#存入临时缓冲等待计算current

= data['datetime']#每隔interval计算buffer中的数据一次if(current

- start).total_seconds() >= interval:

ret

= handler(buffer)print('{:.2f}'.format(ret))

start

= current#清除超出width的数据buffer

= [xforxinbufferifx['datetime']

> current - delta]#处理函数,送入一批数据计算一个结果defhandler(iterable):returnsum(map(lambdaitem:

item['value'],iterable))

/len(iterable)

window(source(),handler,10,5)

blog_18a1100b10102yhwi.html

分发

生产者消费者模型

对于一个监控系统,需要处理很多数据,包括日志。对其中已有数据的采集、分析。

被监控对象就是数据的生产者producer,数据的处理程序就是数据的消费者consumer

生产者消费者传统模型blog_18a1100b10102yhwi.html

传统的生产者消费者模型,生产者生产,消费者消费。但这种模型有问题

开发的代码耦合高,如果生成规模扩大,不易扩展,生产和消费的速度很难匹配等。

解决办法:队列

作用:解耦、缓冲blog_18a1100b10102yhwi.html

日志生产者往往会部署好几个程序,日志产生的也很多,而消费者也会有很多个程序,去提取日志分析处理

数据生产是不稳定的。会造成段时间数据的潮涌,需要缓冲

消费者消费能力不一样,有快有慢,消费者可以自己决定消费缓冲区中的数据

单机可以使用queue内建的模块构建进程内的队列,满足多个线程间的生产消费需要

大型系统可以使用第三方消息中间件:RabbitMQ、RocketMQ、Kafka

queue模块——队列

queue.Queue(maxsize=0)

创建FIFO队列,返回Queue对象

maxsize小于等于0,队列长度没有限制

Queue.get(block=True,timeout=None)

从队列中移除元素并返回这个元素

block阻塞,timeout超时

如果block为True,是阻塞,timeout为None就是一直阻塞

如果block为True但是timeout有值,就阻塞到一定秒数抛出异常

block为False,是非阻塞,timeout将被忽略,要么成功返回一个元素,要么抛出empty异常

Queue.get_nowait()

等价于get(False)

Queue.put(item,block=True,timeout=None)

把一个元素加入到队列中去

block=True,timeout=None,一直阻塞直至有空位放元素

block=True,timeout=5,阻塞5秒就抛出Full异常

block=True,timeout失效,立刻返回,,一直阻塞直至有空位放元素

Queue.put_nowait(item)

等价于put(item,False)

分发器实现

实现多线程t

= threading.Thread(target=函数名(add),args=参数(4,5))方法

t.start()启动线程

生产者(数据源)生产数据,缓冲到消息队列中

数据处理流程:

数据加载->提取->分析(滑动窗口函数)

处理大量数据的时候,可能需要多个消费者处理

需要一个分发器(调度器),把数据分发给不同的消费者处理

每一个消费者拿到数据后,有自己的处理函数。所以要有一种注册机制

数据加载->提取->分发->分析函数1&分析函数2

分析1和分析2可以是不同的handler、窗口宽度、间隔时间

暂时采用轮询策略,一对多的副本发送,一个数据通过分发器、发送到多个消费者

消息队列

在生产者和消费者之间使用消息队列,那么所有消费者可以共有一个消息队列,或各自拥有一个消息队列

公用一个消息队列需要解决争抢问题。每个消费者拥有一个队列较易实现

注册

在调度器内部记录消费者,每一个消费者拥有自己的队列

线程

由于一条数据会被多个不同的注册过的handler处理,所以最好的方式就是多线程

分发器代码实现

importrandomimportdatetimeimporttimefromqueueimportQueueimportthreading

defsource(second=1):"""生成数据"""while

True:yield{'datetime':

datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=8))),'value':

random.randint(1,100),}

time.sleep(second)#平均数defavg_handler(iterable):returnsum(map(lambdaitem:

item['value'],iterable))

/len(iterable) defwindow(q:

Queue,handler,width:int,interval:int):

start

= datetime.datetime.strptime('20170101

00:00:01 +0800','%Y%m%d

%H:%M:%S %z')

current

= datetime.datetime.strptime('20170101

00:00:01 +0800','%Y%m%d

%H:%M:%S %z')

buf

= []#窗口的待计算数据delta

= datetime.timedelta(seconds=width

- interval)while

True:#从数据源获取数据data

= q.get()#阻塞的ifdata:

buf.append(data)#存入临时缓冲等待计算current

= data['datetime']print(current,start)#每隔interval计算buffer中的数据一次if(current

- start).total_seconds() > interval:print('---------------')

ret

= handler(buf)print('{:.2f}'.format(ret))print(threading.current_thread())

start

= current#清除超出width的数据buf

= [xforxinbufifx['datetime']

> current - delta]defdispatcher(src):

handlers

= []#分发器中记录handler,同时保存各自的队列queues

= []defreg(handler,width:int,interval:int):

q

= Queue()

t

= threading.Thread(target=window,args=(q,handler,width,interval))

queues.append(q)

handlers.append(t)defrun():fortinhandlers:

t.start()#启动线程处理数据while

True:

date

=next(src)#改这里数据源不同forqinqueues:

q.put(date)returnreg,run

reg,run

= dispatcher(source())

reg(avg_handler,10,5)print(threading.current_thread())

run()

整合代码

load函数就是从日志中提取合格的数据生成函数

它可以作为dispatcher函数的数据源

importrandomimporttimefromqueueimportQueueimportthreadingimportdatetimeimportrefrompathlibimportPathfromuser_agentsimportparse

pattern ='''(?P[\d.]{7,}) - - \[(?P[/\w +:]+)\]\"(?P\w+) (?P\S+) (?P[\w/\d.]+)"\(?P\d+) (?P\d+) .+ "(?P.+)"'''regex = re.compile(pattern)

ops = {'datetime':lambdadstr: datetime.datetime.strptime(dstr,'%d/%b/%Y:%H:%M:%S %z'),'status':int,'size':int}defextract(line:str) ->dict:"""返回字段的字典,如果返回None说明匹配失败"""matcher = regex.match(line)ifmatcher:return{k: ops.get(k, lambdax: x)(v)fork,vinmatcher.groupdict().items()}else:#输出日志, 记录不合格returnline# None

# raise Exception{

# 'No match. {}'.format(line)

# }

#数据源defloadfile(filename:str,encoding='utf-8'):"""装载日志文件"""withopen(filename,encoding=encoding)asf:forlineinf:

fields = extract(line)ifisinstance(fields,(dict,)):yieldfieldselse:# print('No match. {}'.format(fields)) #TODO解析失败就抛弃,或者打印日志pass

defload(*paths,encoding='utf-8',ext='*.log',r=False):forpinpaths:

path = Path(p)ifpath.is_dir():ifisinstance(ext,str):

ext = [ext]foreinext:

logs = path.glob(e)ifrelsepath.glob(e)#遍历当前目录forloginlogs:# Path对象yield fromloadfile(str(log.absolute()),encoding=encoding)# for x in loadfile(str(log.absolute())):

# yield xelifpath.is_file():yield fromloadfile(str(path.absolute()),encoding=encoding)#模拟用的数据源defsource(second=1):"""生成数据"""while True:yield{'datetime': datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=8))),'value': random.randint(1,100)}

time.sleep(second)# s = source()

#分析函数, 处理函数#平均数defavg_handler(iterable):returnsum(map(lambdaitem: item['value'],iterable)) /len(iterable)#状态码分析defstatus_handler(iterable:list):#指的是一个时间段内的数据list(dicts)status = {}foriteminiterable:# item => fieldskey = item['status']

status[key] = status.get(key,0) +1total =len(iterable)return{k: v/totalfork,vinstatus.items()}

allbrowser = {}#浏览器分析defbrowser_handler(iterable):

browser = {}foriteminiterable:# item => fieldsuastr = item['useragent']

ua = parse(uastr)

key = ua.browser.family,ua.browser.version_string

browser[key] = browser.get(key,0) +1allbrowser[key] = allbrowser.get(key,0) +1returnbrowser#窗口函数defwindow(q: Queue,handler,width:int,interval:int):

start = datetime.datetime.strptime('20170101 00:00:01 +0800','%Y%m%d %H:%M:%S %z')

current = datetime.datetime.strptime('20170101 00:00:01 +0800','%Y%m%d %H:%M:%S %z')

buf = []#窗口的待计算数据delta = datetime.timedelta(seconds=width - interval)while True:#从数据源获取数据data = q.get()#阻塞的消息队列ifdata:

buf.append(data)#存入临时缓冲等待计算current = data['datetime']print(current,start)#每隔interval计算buffer中的数据一次if(current - start).total_seconds() > interval:print('---------------')

ret = handler(buf)print('{}'.format(ret))print(threading.current_thread())

start = current#清除超出width的数据buf = [xforxinbufifx['datetime'] > current - delta]#还有持久化、可视化#分发器,觉定者数据的调度defdispatcher(src):

handlers = []#分发器中记录handler,同时保存各自的队列queues = []defreg(handler,width:int,interval:int):

q = Queue()

t = threading.Thread(target=window,args=(q,handler,width,interval))

queues.append(q)

handlers.append(t)defrun():fortinhandlers:

t.start()#启动线程处理数据while True:# date = next(src) #改这里数据源不同fordatainsrc:forqinqueues:

q.put(data)returnreg,run

if__name__ =='__main__':

src = load('.')#生成器reg,run = dispatcher(src)

reg(status_handler,10,5)

reg(browser_handler,10,10)# print(threading.current_thread())run()

完成分析功能

分析日志很重要,通过海量数据分析就能够知道是否遭受了***,是否被爬取及爬取高峰期,是否有盗链等

状态码分析

状态码中包含了很多信息。例如

304,服务器收到客户端提交的请求参数,发现资源未变化,要求浏览器使用静态资源的缓存

404,服务器找不到大请求的资源

304占比大,说明静态缓存效果明显。404占比大,说明网站出现了错误链接,或者尝试嗅探网站资源

如果400、500占比突然增大,网站一定出了问题。

defstatus_handle(iterable):

status

= {}foriteminiterable:

key

= item['status']

status[key]

= status.get(key,0)

+1total

=len(iterable)return{k:

v / totalfork,vinstatus.items()}

浏览器分析

useragent

这里指的是,软件按照一定的格式向远端的服务器提供一个表示自己的字符串

在HTTP协议中,使用useragent字段传送这个字符串

浏览器选项中可以修改此设置

信息提取

安装

pipinstallpyyaml

ua-parseruser-agents

数据分析

defbrowser_handler(iterable):

browser

= {}foriteminiterable:

uaster

= item['useragent']

ua

= parse(uaster)

key

= ua.browser.family,ua.browser.version_string

browser[key]

= browser.get(key,0)

+1returnbrowser

谢谢观看!!!

sg_trans.gif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值