01-结构化数据模型选择(理解)
问题1: 原生python中有没有数据库中的表类型?
没有
问题2: 我们一般会使用什么数据类型存储数据库中读取到的数据值?
列表嵌套字典: 记录数据和书写数据,非常方便,但是我们需要记住每一个键(字段)的含义,如果字段过多,该方法基本无法使用(
字典的键不记得是取不出值的
)元组嵌套元组: 结构简单,便于统计,但是无法记录字段名称,在字段顺序不固定或者字段数量过多时,无法使用
列表嵌套对象: 前期书写代码量比较大,但是后期使用即为方便.获取字段值可以使用代码提示
问题3: 那种存储方式更利于我们调用数据?
使用列表嵌套对象的方式保存数据集,我们可以更加灵活的存储或调用数据
在ETL开发中一般会将每一条数据库记录映射为一个对象
多个对象保存在一个列表中就保存了整张表的数据
id | name | age |
---|---|---|
1 | 宝强 | 32 |
2 | 乃亮 | 35 |
3 | 羽凡 | 37 |
class Person(object):
def __init__(self, id, name, age):
self.id = id
self.name = name
self.age = age
person_list = [
Person(1, '宝强', 32),
Person(2, '乃亮', 35),
Person(3, '羽凡', 37)
]
csv
id,name,age # 标头
1,宝强,32
2,乃亮,35
3,羽凡,37
在开发中上述代码中的Person就是一个模型
02-简述模型类的使用(理解)
问题1: 什么是模型?
将结构化数据,保存到对象中,每一个对象就是一条数据记录,实例属性名就是列名,实例属性的值就是数据记录的值
多个模型对象,就组成了一个数据集.
问题2: 怎样创建模型类呢?
- 将源数据转换为结构化数据
- 将结构化数据中的列索引(字段名)作为模型对象的属性名,构建一个对象
- 在创建模型时可以手动传入属性值,快速构建模型对象
问题3: 模型类可以应用在哪里?
以本案例为例
- 我们可以利用模型对象,快速生成一条插入数据的sql语句
- 我们可以利用模型对象快速生成一个csv格式的文件数据
"""
模型的数据从哪来???
json数据文件读取
log日志数据文件读取
mysql数据中读取
数据怎样加载到模型类中呢?
在创建对象时从传入
"""
import json
# 1. 创建一个PersonModel模型类
class PersonModel(object):
def __init__(self, json_data: str):
"""书写一个init方法将json数据解析,并保存到每一个属性中"""
# 1. 解析json数据 loads可以将json数据解析为python中的数据类型
data = json.loads(json_data)
# 2. 将json数据的解析结果写入属性中
self.id = data['id']
self.name = data['name']
self.age = data['age']
self.gender = data['gender']
self.register_date = data['register_date']
def generate_insert_sql(self):
"""使用模型类生成一个sql插入语句"""
# 返回一个sql语句,对于属性值进行拼接
# 生成sql语句时字符串类型数据要在内部添加引号(除了数值型,mysql中的数据都需要加引号)
return f'insert into 表名(name,age,gender,register_date) values(' \
f'"{self.name}",' \
f'{self.age},' \
f'"{self.gender}",' \
f'"{self.register_date}");'
def generate_csv_header(self, sep=','):
"""使用模型类生成一个csv数据标头"""
return f'id{sep}' \
f'name{sep}' \
f'age{sep}' \
f'gender{sep}' \
f'register_date\n'
def generate_csv_str(self, sep=','):
"""使用模型类生成一个csv数据内容"""
return f'{self.id}{sep}' \
f'{self.name}{sep}' \
f'{self.age}{sep}' \
f'{self.gender}{sep}' \
f'{self.register_date}\n'
if __name__ == '__main__':
# 1. 读取数据
file = open('../source/students', 'r', encoding='utf8')
content = file.readline()
# 2. 构建模型
p1 = PersonModel(content)
# 3.验证输出
print(p1.generate_insert_sql())
print(p1.generate_csv_header())
print(p1.generate_csv_str())
03-构造模拟日志数据(了解)
/simulator/backend_logs_simulator.py
"""
后端服务写出log日志的模拟数据生成器
"""
import datetime
import random
import time
single_log_lines = 1024 # 一个logs文件生成多少行数据
generate_files = 5 # 一次运行生成多少个文件
output_path = "D:/etl/logs/"
log_level_array = ['WARN', 'WARN', 'WARN', 'INFO', 'INFO', 'INFO', 'INFO', 'INFO', 'INFO', 'INFO', 'INFO',
'INFO', 'INFO', 'INFO', 'INFO', 'INFO', 'INFO', 'INFO', 'INFO', 'INFO', 'INFO', 'INFO', 'INFO',
'ERROR']
backend_files_name = ['barcode_service.py', 'barcode_service.py', 'barcode_service.py',
'orders_service.py', 'orders_service.py', 'orders_service.py', 'orders_service.py',
'orders_service.py', 'orders_service.py',
'shop_manager.py', 'shop_manager.py',
'user_manager.py', 'user_manager.py', 'user_manager.py',
'goods_manager.py', 'goods_manager.py', 'goods_manager.py', 'goods_manager.py',
'goods_manager.py', 'goods_manager.py',
'base_network.py', 'base_network.py',
'event.py', 'event.py', 'event.py', 'event.py', 'event.py', 'event.py', 'event.py']
visitor_areas = {
'北京市': ['海淀区', '大兴区', '丰台区', '朝阳区', '昌平区', '海淀区', '怀柔区'],
'上海市': ['静安区', '黄浦区', '徐汇区', '普陀区', '杨浦区', '宝山区', '浦东新区', '浦东新区'],
'重庆市': ['万州区', '万州区', '涪陵区', '渝中区', '沙坪坝区', '九龙坡区', '南岸区'],
'江苏省': ['南京市', '南京市', '南京市', '苏州市', '苏州市', '无锡市', '常州市', '宿迁市', '张家港市'],
'安徽省': ['阜阳市', '阜阳市', '六安市', '合肥市', '合肥市', '合肥市', '池州市', '铜陵市', '芜湖市'],
'山东省': ['济南市', '济南市', '青岛市', '青岛市', '青岛市', '菏泽市'],
'湖北省': ['武汉市', '武汉市', '武汉市', '十堰市', '荆州市', '恩施土家族苗族自治州'],
'广东省': ['广州市', '广州市', '广州市', '深圳市', '深圳市', '深圳市', '珠海市'],
'天津市': ['和平区', '河东区', '河西区', '武清区', '宝坻区'],
'湖南省': ['长沙市', '长沙市', '长沙市', '长沙市', '长沙市', '长沙市', '长沙市', '株洲市', '张家界市', '常德市', '益阳市'],
'浙江省': ['杭州市', '杭州市', '湖州市', '绍兴市', '舟山市', '金华市', '嘉兴市', '丽水市']
}
visitor_province = ['北京市', '上海市', '重庆市', '江苏省', '安徽省', '山东省', '湖北省', '广东省', '天津市', '湖南省', '浙江省']
response_flag = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]
response_for_error_flag = [1, 1, 1, 1, 1, 0]
for j in range(0, generate_files):
write_file_path = f'{output_path}{datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")}.log'
with open(write_file_path, 'w', encoding="UTF-8") as f:
for i in range(single_log_lines):
date_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
log_level = log_level_array[random.randint(0, len(log_level_array) - 1)]
file_name = backend_files_name[random.randint(0, len(backend_files_name) - 1)]
if not log_level == "ERROR":
if response_flag[random.randint(0, len(response_flag) - 1)] == 1:
response_time = random.randint(0, 1000)
else:
response_time = random.randint(1000, 9999)
else:
if response_for_error_flag[random.randint(0, len(response_for_error_flag) - 1)] == 1:
response_time = random.randint(0, 1000)
else:
response_time = random.randint(1000, 9999)
province = visitor_province[random.randint(0, len(visitor_province) - 1)]
city = visitor_areas[province][random.randint(0, len(visitor_areas[province]) - 1)]
log_str = f"{date_str}\t[{log_level}]\t{file_name}\t响应时间:{response_time}ms\t{province}\t{city}\t" \
f"这里是日志信息......"
f.write(log_str)
f.write("\n")
print(f"本次写出第: {j + 1}个文件完成, 文件为: {write_file_path}, 行数:{single_log_lines}")
time.sleep(1)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kFNtu6GD-1688341359616)(day03-日志数据采集&商品数据采集.assets/1672469646098.png)]
注意: 我们的日志信息每一行数据,就是一条日志记录,日志的各项信息之间用制表位进行分隔(\t)
04- 新增配置文件信息(理解)
- 待采集目录路径
- 元数据表的相关信息
- 目标数据表的相关信息
- csv文件的导出位置
config/project_config.py
# ################## --后台日志数据采集配置项-- ###################
# 待采集 日志 文件所在的目录
backend_logs_data_root_path = 'D:/etl/logs'
# 采集后台日志数据,元数据表配置项
logs_monitor_meta_table_name = "backend_logs_monitor"
logs_monitor_meta_table_create_cols = \
"id INT PRIMARY KEY AUTO_INCREMENT, " \
"file_name VARCHAR(255) NOT NULL COMMENT '处理文件名称', " \
"process_lines INT NULL COMMENT '文件处理行数', " \
"process_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '文件处理时间'"
# 后台日志表名称
target_logs_table_name = "backend_logs"
target_logs_table_create_cols = \
f"id int PRIMARY KEY AUTO_INCREMENT COMMENT '自增ID', " \
f"log_time TIMESTAMP(6) COMMENT '日志时间,精确到6位毫秒值', " \
f"log_level VARCHAR(10) COMMENT '日志级别', " \
f"log_module VARCHAR(50) COMMENT '输出日志的功能模块名', " \
f"response_time INT COMMENT '接口响应时间毫秒', " \
f"province VARCHAR(30) COMMENT '访问者省份', " \
f"city VARCHAR(30) COMMENT '访问者城市', " \
f"log_text VARCHAR(255) COMMENT '日志正文', " \
f"INDEX(log_time)"
# 后台日志数据写出 csv 的根路径
logs_output_csv_root_path = "D:/etl/output/backend_logs"
# 每一次运行,后台日志文件写出路径
logs_output_csv_file_name = \
f"orders-{time.strftime('%Y-%m-%d-%H_%M', time.localtime())}.csv"
05-构建日志数据模型类(掌握)
如何将日志文件转换为模型对象呢?
- 将日志文件逐行读取出来,
- 按照规则将日志文件拆分多个字符串,分隔符为\t
- 将每一个字符串进行处理.保存到不同的属性名中
模型类又哪些方法呢?
- 初始化方法
- 生成sql插入语句方法
- 生成csv文件标头内容方法
- 生成csv文件数据行方法
model/backend_logs_model.py
注意点:
- sql语句中插入字符串要加引号
- csv文件每插入一行后要加\n
from config import project_config as conf
class BackendLogsModel(object):
"""创建一个模型类"""
def __init__(self, log_data: str):
"""初始化方法"""
# 1. 将log_data数据拆分为列表,分隔符是\t
data = log_data.split('\t')
# 2. 将列表中的每一个元素赋值给相应的属性
self.log_time = data[0]
self.log_level = data[1].strip('[]')
self.log_module = data[2]
self.response_time = data[3][5:-2]
self.province = data[4]
self.city = data[5]
self.log_text = data[6]
def generate_insert_sql(self):
"""生成插入数据的sql语句"""
return f'insert into {conf.target_logs_table_name}(' \
f'log_time, log_level, log_module, response_time, province, city, log_text)' \
f' values("{self.log_time}",' \
f'"{self.log_level}",' \
f'"{self.log_module}",' \
f'"{self.response_time}",' \
f'"{self.province}",' \
f'"{self.city}",' \
f'"{self.log_text}");'
@staticmethod
def generate_csv_header(sep=','):
"""生成csv文件的标头"""
return f'log_time{sep}' \
f'log_level{sep}' \
f'log_module{sep}' \
f'response_time{sep}' \
f'province{sep}' \
f'city{sep}' \
f'log_text\n'
# 生成csv文件的行内容
def gender_csv_str(self, sep=','):
return f'{self.log_time}{sep}' \
f'{self.log_level}{sep}' \
f'{self.log_module}{sep}' \
f'{self.response_time}{sep}' \
f'{self.province}{sep}' \
f'{self.city}{sep}' \
f'{self.log_text}\n'
06-日志数据采集业务(理解)
业务需求:
将采集的后台日志数据保存到目标数据库 ‘target’ 中
将采集的后台日志数据写出到 CSV 文件中
实现思路:
- 获取后台访问日志文件夹下面有哪些日志文件
- 查询元数据库表中已经被采集的日志文件,来对比确定要采集新的访问日志文件
- 针对待采集的新访问日志文件,进行数据采集(ETL操作->mysql->csv)
- 将本次采集的访问日志文件,记录到元数据库的表中
07-核心业务数据实现(理解,并跟着老师的代码敲一遍即可)
7.1 获取待采集文件列表
- 获取后台访问日志文件夹下面有哪些日志文件
- 查询元数据库表中已经被采集的日志文件,来对比确定要采集新的访问日志文件
- 创建元数据库连接
- 获取已经被采集的日志文件
- 对比确定要采集的日志文件
backend_logs_service.py
"""
1. 获取后台访问日志文件夹下面有哪些日志文件
2. 查询元数据库表中已经被采集的日志文件,来对比确定要采集新的访问日志文件
- 创建元数据库连接
- 获取已经被采集的日志文件
- 对比确定要采集的日志文件
"""
# 0. 导包
from util import file_util
from util import mysql_util
from config import project_config as conf
# 1. 获取后台访问日志文件夹下面有哪些日志文件(file_util)
all_file_list = file_util.get_dir_files_list(conf.backend_logs_data_root_path)
# 2. 查询元数据库表中已经被采集的日志文件,来对比确定要采集新的访问日志文件
# 2.1 创建元数据库连接(mysql_util)
metadata_util = mysql_util.get_mysql_util(
host=conf.metadata_host,
port=conf.metadata_port,
user=conf.metadata_user,
password=conf.metadata_password
)
# 2.2 获取已经被采集的日志文件(mysql_util)
processed_file_list = mysql_util.get_processed_files(
util=metadata_util,
db_name=conf.metadata_db,
tb_name=conf.logs_monitor_meta_table_name,
tb_cols=conf.logs_monitor_meta_table_create_cols
)
# 2.3 对比确定要采集的日志文件(file_util)
new_file_list = file_util.get_new_by_compare_lists(processed_file_list, all_file_list)
7.2 采集数据并写入mysql数据库和csv文件中
逐行读取数据的方法
"""
# 读取数据的方式???
read() 读取文件中的所有数据,在大数据场景下几乎不用
read(n) 读取指定的字节数或者字符数,一般n是1024的倍数 8*1024
readline() 一般只适用于字符型文件
readlines() 按行读取文件中的所有数据,在大数据场景下几乎不用
# 文件打开的方式有哪些???
file = open
with open as file
"""
# 如果我们想要按行读取一个文件中的全部数据,应该怎么做???
# 方法一: 打开文件,读取每一行文件,当读取的内容为空时,证明已经读取完成,关闭文件结束程序
with open('../logs/test.log', 'r', encoding='utf8') as file:
# 创建死循环,只有当读取内容为空时才能跳出循环,此时文件读取完成
while True:
content = file.readline()
if not content:
break
else:
print(content)
print('文件读取完成')
# 方法二(推荐): for i in open
for row_content in open('../logs/test.log', 'r', encoding='utf8'):
print(row_content)
print('文件读取完成')
- 针对待采集的新访问日志文件,进行数据采集(ETL操作->mysql->csv)
- 创建目标csv文件
- 向csv文件中写入标头
- 创建目标数据库连接
- 检查目标数据表是否存在,不存在则创建
- 遍历要采集的日志文件
- 遍历每一行数据,生成模型对象
- 写入到数据库中
- 写入到csv文件中
# 3. 针对待采集的新访问日志文件,进行数据采集(ETL操作->mysql->csv)
# 3.1 创建目标csv文件
csv_file = open(conf.logs_output_csv_root_path + conf.logs_output_csv_file_name,
'a',
encoding='utf8')
# 3.2 向csv文件中写入标头
csv_file.write(BackendLogsModel.generate_csv_header())
# 3.3 创建目标数据库连接
target_util = mysql_util.get_mysql_util(
host=conf.metadata_host,
port=conf.metadata_port,
user=conf.metadata_user,
password=conf.metadata_password
)
# 3.4 检查目标数据表是否存在,不存在则创建
target_util.check_table_exists_and_create(
db_name=conf.metadata_db,
tb_name=conf.target_logs_table_name,
tb_cols=conf.target_logs_table_create_cols
)
# 3.5 遍历要采集的日志文件(从2.3步获取的new_file_list)
for file_path in new_file_list:
# 3.6 遍历每一行数据,
for row_content in open(file_path, 'r', encoding='utf8'):
# 3.6.1 生成模型对象
backend_log_model = BackendLogsModel(row_content)
# 3.6.2写入到数据库中
target_util.insert_single_sql(backend_log_model.generate_insert_sql())
# 3.6.3写入到csv文件中
csv_file.write(backend_log_model.gender_csv_str())
# 3.7 关闭csv文件
csv_file.close()
# 3.8 关闭数据库连接
target_util.close()
7.3 给采集过程添加事务
核心: 一个文件全部采集完成才提交
当一个文件采集到一半出现异常后,需要进行代码的回滚操作,防止出现采集一半的情况,因为我们的采集单位是文件
逻辑:
开启事务,如果在采集过程中出现异常,则回滚
注意: 此处没有处理csv文件的回滚问题
# 3. 针对待采集的新访问日志文件,进行数据采集(ETL操作->mysql->csv)
# 3.1 创建目标csv文件
csv_file = open(conf.logs_output_csv_root_path + conf.logs_output_csv_file_name,
'a',
encoding='utf8')
# 3.2 向csv文件中写入标头
csv_file.write(BackendLogsModel.generate_csv_header())
# 3.3 创建目标数据库连接
target_util = mysql_util.get_mysql_util(
host=conf.metadata_host,
port=conf.metadata_port,
user=conf.metadata_user,
password=conf.metadata_password
)
# 3.4 检查目标数据库书否存在,不存在则创建
target_util.check_table_exists_and_create(
db_name=conf.metadata_db,
tb_name=conf.target_logs_table_name,
tb_cols=conf.target_logs_table_create_cols
)
# 3.5 遍历要采集的日志文件(从2.3步获取的new_file_list)
for file_path in new_file_list:
# 事务1: 开启事务
target_util.begin_transaction()
try:
# 3.6 遍历每一行数据,
for row_content in open(file_path, 'r', encoding='utf8'):
# 3.6.1 生成模型对象
backend_log_model = BackendLogsModel(row_content)
# 3.6.2写入到数据库中
target_util.insert_single_sql(backend_log_model.generate_insert_sql())
# 3.6.3写入到csv文件中
csv_file.write(backend_log_model.gender_csv_str())
except Exception as e:
# 事务2: 事务回滚
target_util.rollback_transaction()
else:
# 事务3: 一个文件处理完成后提交事务
target_util.commit_transaction()
# 3.7 关闭csv文件
csv_file.close()
# 3.8 关闭数据库连接
target_util.close()
注意: 要在Util中添加一个不自动提交的插入方法
util/mysql_util.py
def insert_single_sql_without_commit(self, sql):
"""执行一条插入SQL"""
# 为什么要单独穿件一个插入方法,因为插入时可能存在一些异常,我们需要记录异常,方便后续对于数据的修改和监控
try:
# 1. 执行插入sql语句,使用pymysql原生的execute方法将不会自动提交
self.conn.cursor().execute(sql)
except Exception as e:
# 2. 捕获异常
# 2.1 如果出现异常,则记录日志
logger.warning(f'sql执行出现异常,异常信息为: {e}')
# 2.2 将异常继续抛出,防止已经错误的程序继续执行
# 使用raise指令可以手动抛出异常
raise e
# 3.如果没有异常则记录日志信息
logger.info(f'插入sql语句:{sql}执行成功,没有异常出现')
将自动提交的插入方法,改为非自动提交的插入方法
# 3. 针对待采集的新访问日志文件,进行数据采集(ETL操作->mysql->csv)
# 3.1 创建目标csv文件
csv_file = open(conf.logs_output_csv_root_path + conf.logs_output_csv_file_name,
'a',
encoding='utf8')
# 3.2 向csv文件中写入标头
csv_file.write(BackendLogsModel.generate_csv_header())
# 3.3 创建目标数据库连接
target_util = mysql_util.get_mysql_util(
host=conf.metadata_host,
port=conf.metadata_port,
user=conf.metadata_user,
password=conf.metadata_password
)
# 3.4 检查目标数据库书否存在,不存在则创建
target_util.check_table_exists_and_create(
db_name=conf.metadata_db,
tb_name=conf.target_logs_table_name,
tb_cols=conf.target_logs_table_create_cols
)
# 3.5 遍历要采集的日志文件(从2.3步获取的new_file_list)
for file_path in new_file_list:
# 事务1: 开启事务
target_util.begin_transaction()
try:
# 3.6 遍历每一行数据,
for row_content in open(file_path, 'r', encoding='utf8'):
# 3.6.1 生成模型对象
backend_log_model = BackendLogsModel(row_content)
# 3.6.2写入到数据库中
target_util.insert_single_sql_without_commit(backend_log_model.generate_insert_sql())
# 3.6.3写入到csv文件中
csv_file.write(backend_log_model.gender_csv_str())
except Exception as e:
# 事务2: 事务回滚
target_util.rollback_transaction()
else:
# 事务3: 一个文件处理完成后提交事务
target_util.commit_transaction()
# 3.7 关闭csv文件
csv_file.close()
# 3.8 关闭数据库连接
target_util.close()
7.4 将采集完成的文件信息记录到元数据库
- 将本次采集的访问日志文件,记录到元数据库的表中
backend_logs_service.py
# 3.5 遍历要采集的日志文件(从2.3步获取的new_file_list)
for file_path in new_file_list:
# 事务1: 开启事务
target_util.begin_transaction()
row_total = 0
try:
# 3.6 遍历每一行数据,
for row_content in open(file_path, 'r', encoding='utf8'):
# 3.6.1 生成模型对象
backend_log_model = BackendLogsModel(row_content)
# 3.6.2写入到数据库中
target_util.insert_single_sql_without_commit(backend_log_model.generate_insert_sql())
# 3.6.3写入到csv文件中
csv_file.write(backend_log_model.gender_csv_str())
row_total += 1
except Exception as e:
# 事务2: 事务回滚
# log日志一般是按照时间排序的,所以如果出现回滚回滚后会结束 程序
target_util.rollback_transaction()
else:
# 事务3: 一个文件处理完成后提交事务
target_util.commit_transaction()
# 4. 将本次采集的访问日志文件,记录到元数据库的表中
# 4.1 创建sql语句
sql = f'insert into {conf.logs_monitor_meta_table_name}(' \
f'file_name, process_lines) values' \
f'("{file_path}","{row_total}");'
# 4.2 执行sql语句记录到元数据库中
metadata_util.insert_single_sql(sql)
7.5 添加logging日志
- 日志采集开始时记录日志
- 记录本次采集的新数据文件有哪些
- 日志采集开始时记录日志,并显示数据采集时间
backend_logs_service.py
"""
1. 获取后台访问日志文件夹下面有哪些日志文件
2. 查询元数据库表中已经被采集的日志文件,来对比确定要采集新的访问日志文件
- 创建元数据库连接
- 获取已经被采集的日志文件
- 对比确定要采集的日志文件
3. 针对待采集的新访问日志文件,进行数据采集(ETL操作->mysql->csv)
- 创建目标csv文件
- 向csv文件中写入标头
- 创建目标数据库连接
- 检查目标数据库书否存在,不存在则创建
- 遍历要采集的日志文件
- 遍历每一行数据,生成模型对象
- 写入到数据库中
- 写入到csv文件中
4. 将本次采集的访问日志文件,记录到元数据库的表中
"""
# 0. 导包
import time
from util import file_util
from util import mysql_util
from util import logging_util
from config import project_config as conf
from model.backend_logs_model import BackendLogsModel
# 日志1: 创建日志对象
logger = logging_util.init_logger()
# 日志2: 记录日志采集开始
logger.info('日志采集开始....')
# 1. 获取后台访问日志文件夹下面有哪些日志文件(file_util)
all_file_list = file_util.get_dir_files_list(conf.backend_logs_data_root_path)
# 2. 查询元数据库表中已经被采集的日志文件,来对比确定要采集新的访问日志文件
# 2.1 创建元数据库连接(mysql_util)
metadata_util = mysql_util.get_mysql_util(
host=conf.metadata_host,
port=conf.metadata_port,
user=conf.metadata_user,
password=conf.metadata_password
)
# 2.2 获取已经被采集的日志文件(mysql_util)
processed_file_list = mysql_util.get_processed_files(
util=metadata_util,
db_name=conf.metadata_db,
tb_name=conf.logs_monitor_meta_table_name,
tb_cols=conf.logs_monitor_meta_table_create_cols
)
# 2.3 对比确定要采集的日志文件(file_util)
new_file_list = file_util.get_new_by_compare_lists(processed_file_list, all_file_list)
# 日志3: 记录采集的新文件有哪些
if not new_file_list:
logger.info('没有待采集文件,所有文件已经采集完成,退出程序...')
exit('日志采集结束...')
else:
logger.info(f'待采集的文件有{new_file_list}...')
# 3. 针对待采集的新访问日志文件,进行数据采集(ETL操作->mysql->csv)
# 3.1 创建目标csv文件
csv_file = open(conf.logs_output_csv_root_path + conf.logs_output_csv_file_name,
'a',
encoding='utf8')
# 3.2 向csv文件中写入标头
csv_file.write(BackendLogsModel.generate_csv_header())
# 3.3 创建目标数据库连接
target_util = mysql_util.get_mysql_util(
host=conf.metadata_host,
port=conf.metadata_port,
user=conf.metadata_user,
password=conf.metadata_password
)
# 3.4 检查目标数据库书否存在,不存在则创建
target_util.check_table_exists_and_create(
db_name=conf.metadata_db,
tb_name=conf.target_logs_table_name,
tb_cols=conf.target_logs_table_create_cols
)
# 日志4: 记录采集开始时间
start_time = time.time()
# 3.5 遍历要采集的日志文件(从2.3步获取的new_file_list)
for file_path in new_file_list:
# 事务1: 开启事务
target_util.begin_transaction()
row_total = 0
try:
# 3.6 遍历每一行数据,
for row_content in open(file_path, 'r', encoding='utf8'):
# 3.6.1 生成模型对象
backend_log_model = BackendLogsModel(row_content)
# 3.6.2写入到数据库中
target_util.insert_single_sql_without_commit(backend_log_model.generate_insert_sql())
# 3.6.3写入到csv文件中
csv_file.write(backend_log_model.gender_csv_str())
row_total += 1
except Exception as e:
# 事务2: 事务回滚
# log日志一般是按照时间排序的,所以如果出现回滚回滚后会结束 程序
target_util.rollback_transaction()
else:
# 事务3: 一个文件处理完成后提交事务
target_util.commit_transaction()
# 4. 将本次采集的访问日志文件,记录到元数据库的表中
# 4.1 创建sql语句
sql = f'insert into {conf.logs_monitor_meta_table_name}(' \
f'file_name, process_lines) values' \
f'("{file_path}","{row_total}");'
# 4.2 执行sql语句记录到元数据库中
metadata_util.insert_single_sql(sql)
# 日志5: 记录结束时间
end_time = time.time()
# 日志6: 记录日志,说明采集用时
logger.info(f'此次采集共采集了{len(new_file_list)}个文件, 共用时{end_time-start_time}s')
# 3.7 关闭csv文件
csv_file.close()
# 3.8 关闭数据库连接
target_util.close()
metadata_util.close()
小bug1: 日志输出了两次
原因:
在
mysql_util
中初始化了一次logger在
backend_logs_service
中又初始化了一次logger此时我们给logger添加了两个FileHandler, 所以使用一次日志对象,将会输出两次日志内容
解决方法:
方法一: 在logger初始化时进行判断,如果已经有了一个handler则不再次添加
方法二: 可以直接调用mysql_util中的logger, 因为我们导入了mysql_util模块则可以使用其中的全局变量
方法三(推荐): 给logger添加名称
logging_util.py
"""
logging工具模块
1. 创建一个Logging类来管理日志器对象
2. 创建init_logger函数,用于快速获取logger日志器对象,以及绑定日志管理器和输出格式
"""
# 使用配置文件的步骤 : 提取>> 导包 >> 替换
# 导入模块
import logging
from config import project_config as conf
class Logging(object):
"""创建一个Logging类来管理logging对象"""
def __init__(self, level=logging.INFO, name=None):
self.logger = logging.getLogger(name)
self.logger.setLevel(level)
def init_logger(name= None):
"""快速获取logger对象,并绑定日志处理器和格式"""
# 1.创建一个日志器对象
logger = Logging(name=name).logger
# 2.创建一个日志处理器对象
file_handler = logging.FileHandler(
filename=conf.log_root_path + conf.log_name,
mode='a',
encoding='utf8'
)
# 3.将日志处理器绑定到日志器对象上
logger.addHandler(file_handler)
# 4.创建一个日志格式
fmt = logging.Formatter(conf.log_format)
# 5.将日志格式绑定到日志处理器上
file_handler.setFormatter(fmt)
# 6.返回logger对象
return logger
if __name__ == '__main__':
"""此处一般是用来写演示代码的"""
# 原因: 因为只有程序员才会查源码,测试人员需要将单元测试写到指定的目录中才能进行测试
logger = init_logger()
logger.info('info方法被调用了')
在使用logger是要给其一个名称
小bug2:重复采集数据
在书写文件路径对比时,没有处理反斜杠
file_util.py
def get_new_by_compare_lists(processed_list, all_list):
"""
获取所有文件路径中,未处理的文件路径信息
:param processed_list: 已经处理过的文件路径列表
:param all_list: 全部的文件路径列表
:return: 未处理的文件路径列表
"""
# 0.创建一个空的列表,用于保存未处理的文件路径
new_list = []
# 1. 循环遍历所有的文件列表
for file_path in all_list:
# 2.与已经处理过的文件列表进行比对
if file_path.replace('\\', '/') not in processed_list:
# 2.1 如果该文件没有被处理过,添加到新的文件目录中
new_list.append(file_path)
# 2.2 如果被处理过什么也不做
# 3. 将new_list进行返回
return new_list