第一个小项目:日志分析处理

日志分析:概述

  1. 生产中会出现大量的系统日志,生成中会生成大量的系统日志、应用程序日志、安全日志等等日志,通过对日志的分析可以了解服务器的负载、健康状况,可以分析客户的分布情况、客户的行为,甚至基于这些分析可以做出预测。
  2. 一般采集流程 日志产出->采集(Logstash、Flume、Scribe)->存储->分析->存储数据库、NoSQL)->可视化
  3. 开源实时日志分析ELK平台Logstash收集日志,并存放到ElasticSearch集群中,Kibana则从ES集群中查询数据生成图表,返回浏览器端

半结构化数据

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

文件分析

  1. 日志是文本文件,需要依赖文件IO、字符串操作、正则表达式等技术。
    通过这些技术就能够把日志中需要的数据提取出来。
写正则表达式
import re
line='''183.60.212.153 - - [19/Feb/2013:10:23:29 +0800] \
"GET /o2o/media.html?menu=3 HTTP/1.1" 200 16691 "-" \
"Mozilla/5.0 (compatible; EasouSpider; +http://www.easou.com/search/spider.html)"'''

pattern='''([\d.]{7,}) - - \[([\w/: +]+)\] "(\w+) (\S+) ([\w\d/.]+)" (\d+) (\d+) ".+" "(.+)"'''
regex=re.compile(pattern)
defextract(line:str):
	m=regex.match(line)
	if m:
		print(m.groups())
extract(line)

类型转换 :

  1. fields中的数据是有类型的,例如时间、状态码等。对不同的field要做不同的类型转换,甚至是自定义的转换
  2. 时间转换:19/Feb/2013:10:23:29 +0800对应格式是%d/%b/%Y:%H:%M:%S %z
    使用的函数是datetime类的strptime方法
import datetime
def convert_time(timestr):
	return datetime.datetime.strptime(timestr,'%d/%b/%Y:%H:%M:%S %z')
	
也可以写成lambda模式
lambdatimestr:datetime.datetime.strptime(timestr,'%d/%b/%Y:%H:%M:%S %z')

状态码和字节数都是整型 直接用int转换就行

映射:对每一个字段命名,然后与值和类型转换的方法对应。最简单的方式,就是使用正则表达式分组。

import datetime
import re
line='''183.60.212.153 - - [19/Feb/2013:10:23:29 +0800] \
"GET /o2o/media.html?menu=3 HTTP/1.1" 200 16691 "-" \
"Mozilla/5.0 (compatible; EasouSpider; +http://www.easou.com/search/spider.html)"'''

pattern='''(?P<remote>[\d.]{7,}) - - \[(?P<datetime>[\w/: +]+)\] \"(?P<method>\w+) (?P<url>\S+) (?P<protocol>[\w\d/.]+)" (?P<status>\d+) (?P<length>\d+) \
".+" "(?P<useragent>.+)"'''
regex=re.compile(pattern)

conversion= {
	'datetime':lambdatimestr:datetime.datetime.strptime(timestr,'%d/%b/%Y:%H:%M:%S %z'),
	'status':int,
 	'length':int
}
def extract(logline:str)->dict:
	m=regex.match(line)
	if m:
		return {k:conversion.get(k,lambdax:x)(v)fork,vinm.groupdict().items()}
print(extract(line))

异常处理:日志中不免会出现一些不匹配的行,需要处理。这里使用re.match方法,有可能匹配不上。所以要增加一个判断采用抛出异常的方式,让调用者获得异常并自行处理。

def extract(logline:str)->dict:
	"""返回字段的字典,抛出异常说明匹配失败"""
	m=regex.match(line)
	if m:
		return {k:conversion.get(k,lambdax:x)(v)fork,vinm.groupdict().items()}
	else:
		raise Exception('No match. {}'.format(line))#或输出日志记录

数据载入:对于本项目来说,数据就是日志的一行行记录,载入数据就是文件IO的读取。将获取数据的方法封装成函数

def load(path):
	"""装载日志文件"""
	with open(path) as f:
		for line in f:
			fields = extract(line)
			if fields:
				yield fields
			else:
				continue# TODO解析失败就抛弃,或者打印日志

滑动窗口

时间窗口分析

概念:
  1. 很多数据,例如日志,都是和时间相关的,都是按照时间顺序产生的。产生的数据分析的时候,要按照时间求值
  2. interval表示每一次求值的时间间隔
  3. width时间窗口宽度,指的一次求值的时间窗口宽度
    滑动窗口

当width = interval

滑动窗口2

当width < interval : 一版不会采纳这种方式,会有数据损失

时序数据:

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

数据分析基本程序结构:

无限的生成随机数函数,产生时间相关的数据,返回时间和随机数的字典
每次取3个数据,求平均值

import random
import datetime
import time

def source(secode=1):
	while True:
		yield {'value' : random.randint(1,100), 'datetime':datetime.datetime.now()}}
		time.sleep(second)#间隔生产一个数据
#攒一批数据
s=source()
items= [next(s)foriinrange(3)]
#处理函数,送入一批数据计算出一个结果,下为平均值
defhandler(iterable):
	returnsum(map(lambdax:x['value'],iterable))/len(iterable)
	
print(items)
print("{:.2f}".format(handler(items)))

# 上面代码模拟了一段时间内产生了数据,等了一段固定的时间获取数据来计算平均值

完整代码

import random
import datetime
import time
from queue import Queue
import threading
import re
# 日志处理正则
pattern = '''(?P<remote>[\d.]{7,}) - - \[(?P<datetime>[\w/: +]+)\] \
"(?P<method>\w+) (?P<url>\S+) (?P<protocol>[\w\d/.]+)" (?P<status>\d+) (?P<length>\d+) \
".+" "(?P<useragent>.+)"'''
# 编译
regex = re.compile(pattern)
from user_agents import parse
conversion = {
    'datetime': lambda timestr: datetime.datetime.strptime(timestr, '%d/%b/%Y:%H:%M:%S %z'),
    'status': int,
    'length': int,
    'useragent': lambda ua: parse(ua)
}
def extract(logline: str) -> dict:
    """返回字段的字典,如果返回None说明匹配失败"""
    m = regex.match(logline)
    if m:
        return {k:conversion.get(k, lambda x:x)(v) for k,v in m.groupdict().items()}
    else:
        return None # 或输出日志记录
# 装载日志数据,数据源
from pathlib import Path
def loadfile(filename:str, encoding='utf-8'):
    """装载日志文件"""
    with open(filename, encoding=encoding) as f:
        for line in f:
            fields = extract(line)
            if isinstance(fields, dict):
                yield fields
            else:
                continue # TODO 解析失败就抛弃,或者打印日志
def load(*paths, encoding='utf-8', ext="*.log", glob=False):
    """装载日志文件"""
    for p in paths:
        path = Path(p)
        if path.is_dir(): # 只处理目录
            if isinstance(ext, str):
                ext = [ext]
            else:
                ext = list(ext)
            for e in ext: # 按照扩展名递归
                files = path.rglob(e) if glob else path.glob(e) # 是否递归
                for file in files:
                    yield from loadfile(str(file.absolute()), encoding=encoding)
        elif path.is_file():
            yield from loadfile(str(path.absolute()), encoding=encoding)
def window(src: Queue, handler, width: int, interval: int):
    """窗口函数
    :param iterator: 数据源,生成器,用来拿数据
    :param handler: 数据处理函数
    :param width: 时间窗口宽度,秒
    :param interval: 处理时间间隔,秒
    """
    if interval > width: # width < interval不处理
        return
    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 = src.get()
        if data: # 攒数据
            buffer.append(data) # 存入临时缓冲等待计算
            current = data['datetime']
        # 每隔interval计算buffer中的数据一次
        if (current - start).total_seconds() >= interval:
            ret = handler(buffer)
            print('{}'.format(ret))
            start = current
            # 保留buffer中未超出width的数据。如果delta为0,说明width等于interval,buffer直接清空
            buffer = [x for x in buffer if x['datetime'] > current - delta] if delta else []
# 处理函数,送入一批数据计算出一个结果,下为平均值
def handler(iterable):
    return sum(map(lambda x: x['value'], iterable)) / len(iterable)
# 测试函数
def donothing_handler(iterable):
    return iterable
# 状态码占比
def status_handler(iterable):
    # 时间窗口内的一批数据
    status = {}
    for item in iterable:
        key = item['status']
        status[key] = status.get(key, 0) + 1
    #total = sum(status.values())
    total = len(iterable)
    return {k:v/total for k,v in status.items()}
# 浏览器分析
allbrowsers = {}
def browser_handler(iterable):
    browsers = {}
    for item in iterable:
        ua = item['useragent']
        key = (ua.browser.family, ua.browser.version_string)
        browsers[key] = browsers.get(key, 0) + 1
        allbrowsers[key] = allbrowsers.get(key, 0) + 1
    print(sorted(allbrowsers.items(), key=lambda x: x[1], reverse=True)[:10])
    return browsers
def dispatcher(src):
    # 分发器中记录handler,同时保存各自的队列
    handlers = []
    queues = []
    def reg(handler, width: int, interval: int):
        """注册 窗口函数
        :param handler: 注册的数据处理函数
        :param width: 时间窗口宽度
        :param interval: 时间间隔
        """
    q = Queue() # 每一个handler自己的数据源queuequeues.append(q)
    # 每一个handler都运行在单独的线程中
    t = threading.Thread(target=window, args=(q, handler, width, interval))
    handlers.append(t)
    def run():
        for t in handlers:
            t.start() # 启动线程,运行所有的处理函数
        for item in src: # 将数据源取到的数据分发到所有队列中
            for q in queues:
                q.put(item)
    return reg, run
if __name__ == '__main__':
    import sys
    #path = sys.argv[1]
    path = 'test.log'
    reg, run = dispatcher(load(path))
    reg(status_handler, 10, 5) # 注册
    reg(browser_handler, 5, 5)
    run() # 运行
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值