日志系统

[toc]

前言:主要事项

1、异常数据的发现及补充、日志分类优化
2、日志数据的文件保存、滚动存储
3、日志文件上传
4、日志回捞

一、日志收集

1、日志分类

1.1、日志目标

序号 目标(Target)
1 app
2 sdk 三方库
3 h5 网页

1.2、日志类型

序号 类型 描述
1 api_app app中的网络请求
2 api_app_cache app中的网络请求的网络缓存请求
3 api_buriedPoint 埋点的网络请求
4 sdk_other sdk的各种事件(初始化等)
5 sdk_api sdk中的网络请求
6 dart 语法
7 widget 视图(布局像素越界等)
8 click_other 点击、
9 click_share 分享
10 native_route 路由/跳转
11 h5_route 与网页跳转有关
12 h5_js js交互
13 monitor_network 监控:网络类型变化
14 monitor_lifecycle 监控:生命周期变化
15 buriedPoint_other 埋点数据生成等
16 im IM
17 heartbeat 心跳
18 other 其他

2、日志等级

序号 目标(Level) 描述
1 Normal 正常信息(目前用于请求开始)
2 Success 成功信息(目前用于请求结束:成功)
3 Warning 警告信息(目前用于请求结束:报错)
4 Error 错误日志(目前用于请求结束:失败)
5 Dangerous 危险(处理白屏等) 一般会进行额外的埋点

3、日志整合归类

序号 列表 标志 包含
1 全部 all 所有
2 警告 warning 所有的警告
3 错误 error 所有的错误
4 接口 api api_app、api_cache(不包括sdk_api、api_buriedPoint)
5 点击 click click_share、click_other、h5_js
6 路由 route navite_route、h5_route
7 网页 H5 h5_route、h5_js
8 sdk sdk sdk_api、sdk_other
9 code code dart、widget
10 埋点 buriedPoint api_buriedPoint、buriedPoint_other
11 监控 monitor monitor_lifecycle、monitor_network
12 其他 other other
13 api结果 api_result type=api_app & level != Normal
14 im im 本地缓存消息、历史消息、收到的消息等
15 心跳 heartbeat

devtool_log_home_page

4、日志的补充收集及其策略

要收集的数据已在上述《1.2、日志类型》中说明。

4.1、页面跳转

详见上述 《3、日志整合归类》中的”路由”

4.2、截断的收集

序号 关键字 场景
1 break for 、 swtich
2 return

4.3、用户关键行为

序号 行为 用途的场景说明
1 用户的资源选择 判断选择的本地资源是否上传到错误的存储桶

二、日志数据的文件保存、滚动存储

1、数据的文件保存

写入xlog文件中。

2、数据的文件滚动存储

2.1、日志的过期清理

1、本地日志文件结构

1
2
3
4
5
6
7
-- Document
-- log
-- 2020-10-01
-- xlog_2020-10-01_1.xlog
-- xlog_2020-10-01_2.xlog
-- 2020-10-18
-- xlog_2020-10-18_1.xlog

2、日志保留清理的配置信息(摘自/同 下文的日志接口中的《日志文件配置信息》)

序号 说明 字段 示例
接口回值1 最多保留几个天的日志目录 maxDayDirCount 7
接口回值2 最多每天保留几个文件 maxDayFileCount 10
接口回值3 每个日志文件超过多少后创建新文件 perFileMaxMB 5

3、清理方案

清理时机:在切换前后台的时候,

  • 日志配置信息的更新
  • 根据最新的日志配置信息,延迟10s后(避免影响启动),进行清理判断及清理
    • 遍历总日志目录 Document/log 下的所有日志文件夹
    • 如果天日志文件夹个数超过保留个数(避免是用保留时间,导致保留时间内只有一个日志,也被删掉),则按顺序删除早期日志
      • 遍历天日志目录 Document/log/yyyy-MM-dd 下的所有日志文件夹
      • 如果天日志文件夹个数超过保留个数(避免是用保留时间,导致保留时间内只有一个日志,也被删掉),则按顺序删除早期日志

2.2、日志文件的滚动写入

要增加的日志记录,如果添加后会超过日志文件的大小,则使用新文件写入。

细节点
1、日志文件的大小判断,不要每次都去读取文件,而是记录住大小变化
2、日志不要有一条写一条,而是使用写入缓冲区+定时器,5s尝试写入一次

3、数据的加密、解密

1、加密:

写入日志字符串时候,对编码后的字节数据,额外增加一个三位随机值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// 使用 GZip 压缩算法将字符串进行压缩,并返回压缩后的字节数据(List<int> 类型)。
List<int> _gzipCompressString(String inputString) {
final codec = GZipCodec();

// 将输入字符串 inputString 编码为 UTF-8 格式的字节数据
final encoded = utf8.encode(inputString);

// 对编码后的字节数据进行压缩,并返回压缩后的字节数据。
final compressedData = codec.encode(encoded);

return compressedData;
}

/// 加密日志字符串
String _encryptLogString(String logString) {
final compressedData = _gzipCompressString(logString); // 压缩字符串,并返回压缩后的字节数据(List<int> 类型)
final randomNumber = Random().nextInt(800) + 100;
final confound = compressedData.toString().substring(0, compressedData.toString().length - 1) + ", $randomNumber]"; // 在压缩后的字符串尾部加上一个随机字符串

return confound;
}

2、解密:

使用python脚本对log文件进行解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import sys
import os
import gzip

# 读取.clog文件并解析数据
def parse_clog_file(input_file):
parsed_data = []

with open(input_file, 'r') as clog_file:
current_data = ""
for line in clog_file:
if current_data:
parsed_data.append(current_data)
binary_data = line[:-6] + ']' # 去除加密时候额外添加的三位随机数
byte_data = bytes(eval(binary_data))
decompressed_data = gzip.decompress(byte_data)
# current_data = byte_data.decode('utf-8')
print(decompressed_data)
current_data = decompressed_data.decode('utf-8')

# 添加最后一个数据条目
if current_data:
parsed_data.append(current_data)

return parsed_data

# 将解析后的数据写入与输入文件同名的.log文件
def write_to_log_file(parsed_data, input_file):
base_name = os.path.splitext(input_file)[0]
output_file = base_name + ".log"

with open(output_file, 'w') as log_file:
for data in parsed_data:
log_file.write(data + "\n")
log_file.write("\n")
log_file.write("=================================================================================================" + "\n")
log_file.write("\n")

if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python script.py <clogFileName>")
sys.exit(1)

input_clog_file = sys.argv[1]

# 解析.clog文件
parsed_data = parse_clog_file(input_clog_file)

# 将解析后的数据写入与输入文件同名的.log文件
write_to_log_file(parsed_data, input_clog_file)

print(f"Log data parsed from {input_clog_file} and written to {input_clog_file}.log.")

三、日志文件上传

1、日志文件命名及目录规范

以用户10000012020-01时候上传声音文件 beautiful_scenery.aac为例,其路径完整示例如下:

1
https://media.xxx.com/app1_test1/audio/1/1000001/2020-01/beautiful_scenery.aac

要上传的存储桶:

序号 说明 参数 约定值示例
1 存储桶region
regionGetFunction
网络环境 ap-shanghai
2 存储桶bucket
bucketGetFunction
网络环境 xxx-pro-image-1302324914
3 上传成功后,桶的映射路径值
cosFileUrlPrefixGetFunction
1、图片桶:https://images.xxx.com/
2、媒体桶:https://media.xxx.com/
3、日志桶:https://static.xxx.com/
4 要上传到桶的哪个相对路径下
cosFileRelativePathGetFunction
上传成功后完整路径=以上桶值+此相对路径
见下文 见下文
eg:app1_test1/audio/1/1000001/2020-01/beautiful_scenery.aac

《要上传到桶的哪个相对路径下 cosFileRelativePathGetFunction 》的文件路径分批说明:

序号 层次说明 层次值算法 层次值描述
1.1 存储桶里相对路径的前缀 app标志_环境标志
eg:app1_pro、app1_test1、app1_dev1
文件区分
1.2 是否在存储桶里进行进一步路径区分(可选) 根据场景决定是否对桶进行进一步分类。
if ( mediaType == xxx ) {
if(mediaScene == yyy) {

}
}

1、都是图片,但自拍图片要求独立出来
2、都是多媒体,但音视频要区分开
2.1 用户1级 int.parse(uid) % 1000; // 取余数 用户名取余,有效减少同层上文件夹个数
2.2 用户2级 uid 用户名
3 年月 DateTime.now().toString().substring(0, 7); 年-月
4 文件名 ${fileOriginName}_
${DateTime.now().microsecondsSinceEpoch}
.$fileExtensionType
文件名

附1:MediaType 媒体类型

序号 UploadMediaType 媒体类型 描述
1 unkonw 未知(默认值)
2 image 图片
3 audio 音频
4 video 视频
5 xlog 日志文件

附2:MediaScene场景 说明

序号 UploadMediaScene场景 描述
1 unkonw 未知(默认值)
2 selfie 自拍(安全等级较多)
3 im 会话聊天
4 live 直播

四、日志接口

1、日志记录接口(区别于回捞接口,需要在回捞前就有日志信息)

1、是否记录日志的开关及其配置信息

序号 说明 字段 示例
网页配置1 是否开启日志记录 isLogOn 1:开启、其他:关
网页配置2 要开启日志的用户(灰度上线)
(当且仅当开启上述日志记录时有效)
📢:未配置但开关为开时为全记录
logUserids [“101”, “102”]
网页配置3 要开启日志的设备(灰度上线)
(当且仅当开启上述日志记录时有效)
📢:未配置但开关为开时为全记录
logDeviceIds [“udid001”, “udid002”]
接口回值1 是否进行日志记录(灰度上线) needLog 1:记录、其他:不记录

2、不需要记录什么日志信息

序号 说明 字段 示例
接口回值1 不需要记录的日志类型 ignoreLogLevel [“Normal”, “Success”]
接口回值2 不需要记录的日志等级 ignoreLogType [“api_buriedPoint”, “buriedPoint_other”]

3、日志文件配置信息

序号 说明 字段 示例
接口回值1 最多保留几个天的日志目录 maxDayDirCount 7
接口回值2 最多每天保留几个文件 maxDayFileCount 10
接口回值3 每个日志文件超过多少后创建新文件 perFileMaxMB 5

4、日志文件问题回滚

序号 说明 字段 示例
网页配置1 强制删除什么时间前的日志 removeLogBeforeDate 2020-12-31
网页配置2 强制删除哪个用户的日志 removeUserids [“101”, “102”]
网页配置2 强制删除哪个设备的日志 logDeviceIds [“udid001”, “udid002”]
接口回值1 强制删除什么时间前的日志(不删除时空) removeLogBeforeDate 2020-12-31 或 空值

接口名定义:

1
function getLoggerConfig(userid, deviceId)

接口结果的json示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"logConfig": {
"needLog": 1,
"ignoreLogLevel": ["Normal", "Success"],
"ignoreLogType": ["api_buriedPoint", "buriedPoint_other"],
"maxDayDirCount": 7,
"maxDayFileCount": 10,
"perFileMaxMB": 5
},
"logRevert": {
"removeLogBeforeDate": "2020-12-31"
}
}

2、日志回捞及问题回滚接口

1、日志回捞

序号 说明 字段 示例
网页配置1 要回捞日志的用户 salvageUserids [“101”, “102”]
网页配置2 要回捞日志的设备 salvageDeviceIds [“udid001”, “udid002”]
网页配置3 日志回捞的开始时间 salvageStartDate 2020-01-01
网页配置4 日志回捞的结束时间 salvageEndDate 2020-01-07
接口回值1 此用户此设备日志回捞的开始时间 salvageStartDate 2020-01-01
接口回值1 此用户此设备日志回捞的结束时间 salvageEndDate 2020-01-07

2、日志文件问题回滚

序号 说明 字段 示例
网页配置1 强制删除什么时间前的日志 removeLogBeforeDate 2020-12-31
网页配置2 强制删除哪个用户的日志 removeUserids [“101”, “102”]
网页配置2 强制删除哪个设备的日志 logDeviceIds [“udid001”, “udid002”]
接口回值1 强制删除什么时间前的日志(不删除时空) removeLogBeforeDate 2020-12-31 或 空值

接口名定义:

1
function getLoggerSalvageAndRevert(salvageUserids, salvageDeviceIds)

接口结果的json示例如下:

1
2
3
4
5
6
{
"salvageId": "1001",
"salvageStartDate": "2020-01-01",
"salvageEndDate": "2020-01-07",
"removeLogBeforeDate": "2020-12-31"
}

3、日志文件上传到cos后同步给后台

用途:日志文件上传到cos后同步给后台,后台数据库记录每个用户,每个设备都捞到了什么数据。

接口名定义:

1
function addLoggerFile(userid, deviceId)

接口结果的json示例如下:

1
2
3
{

}

四、日志回捞

1、后台通过接口返回要回捞的信息。

2、前端用户在下次使用,收到后台信息时候,进行本地日志的上传。在腾云存储桶能收到对应日志,即代表成功。

五、方案实施

灰度方案:请参照 灰度系统 。(附:日志记录开关里目前已有灰度策略。)

目的:避免功能异常,出现集体性问题。

End