ETL日志数据采集&商品数据采集

01-结构化数据模型选择(理解)

问题1: 原生python中有没有数据库中的表类型?

没有

问题2: 我们一般会使用什么数据类型存储数据库中读取到的数据值?

列表嵌套字典: 记录数据和书写数据,非常方便,但是我们需要记住每一个键(字段)的含义,如果字段过多,该方法基本无法使用(字典的键不记得是取不出值的)

元组嵌套元组: 结构简单,便于统计,但是无法记录字段名称,在字段顺序不固定或者字段数量过多时,无法使用

列表嵌套对象: 前期书写代码量比较大,但是后期使用即为方便.获取字段值可以使用代码提示

问题3: 那种存储方式更利于我们调用数据?

使用列表嵌套对象的方式保存数据集,我们可以更加灵活的存储或调用数据

在ETL开发中一般会将每一条数据库记录映射为一个对象

多个对象保存在一个列表中就保存了整张表的数据

idnameage
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: 模型类可以应用在哪里?

以本案例为例

  1. 我们可以利用模型对象,快速生成一条插入数据的sql语句
  2. 我们可以利用模型对象快速生成一个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- 新增配置文件信息(理解)
  1. 待采集目录路径
  2. 元数据表的相关信息
  3. 目标数据表的相关信息
  4. 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-构建日志数据模型类(掌握)

如何将日志文件转换为模型对象呢?

  1. 将日志文件逐行读取出来,
  2. 按照规则将日志文件拆分多个字符串,分隔符为\t
  3. 将每一个字符串进行处理.保存到不同的属性名中

模型类又哪些方法呢?

  1. 初始化方法
  2. 生成sql插入语句方法
  3. 生成csv文件标头内容方法
  4. 生成csv文件数据行方法

model/backend_logs_model.py

注意点:

  1. sql语句中插入字符串要加引号
  2. 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-日志数据采集业务(理解)

业务需求:

  1. 将采集的后台日志数据保存到目标数据库 ‘target’ 中

  2. 将采集的后台日志数据写出到 CSV 文件中

实现思路:

  1. 获取后台访问日志文件夹下面有哪些日志文件
  2. 查询元数据库表中已经被采集的日志文件,来对比确定要采集新的访问日志文件
  3. 针对待采集的新访问日志文件,进行数据采集(ETL操作->mysql->csv)
  4. 将本次采集的访问日志文件,记录到元数据库的表中
07-核心业务数据实现(理解,并跟着老师的代码敲一遍即可)
7.1 获取待采集文件列表
  1. 获取后台访问日志文件夹下面有哪些日志文件
  2. 查询元数据库表中已经被采集的日志文件,来对比确定要采集新的访问日志文件
    • 创建元数据库连接
    • 获取已经被采集的日志文件
    • 对比确定要采集的日志文件

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('文件读取完成')
  1. 针对待采集的新访问日志文件,进行数据采集(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 将采集完成的文件信息记录到元数据库
  1. 将本次采集的访问日志文件,记录到元数据库的表中

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日志
  1. 日志采集开始时记录日志
  2. 记录本次采集的新数据文件有哪些
  3. 日志采集开始时记录日志,并显示数据采集时间

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大数据-Amadeus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值