前言:主要事项
1、异常数据的发现及补充、日志分类优化
2、日志数据的文件保存、滚动存储
3、日志文件上传
4、日志回捞
一、日志收集
1、日志分类
1.1、日志目标
序号 | 目标(Target) | ||
---|---|---|---|
1 | app | ||
2 | sdk | 三方库 | |
3 | h5 | 网页 |
<a name="日志类型"></a>
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 | 危险(处理白屏等) | 一般会进行额外的埋点 |
<a name="日志整合归类"></a>
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 |
<img src="日志系统/devtool_log_home_page.gif" alt="devtool_log_home_page" style="zoom:50%;" />
4、日志的补充收集及其策略
要收集的数据已在上述《1.2、日志类型》中说明。
4.1、页面跳转
详见上述 《3、日志整合归类》中的"路由"
4.2、截断的收集
序号 | 关键字 | 场景 |
---|---|---|
1 | break | for 、 swtich |
2 | return |
4.3、用户关键行为
序号 | 行为 | 用途的场景说明 |
---|---|---|
1 | 用户的资源选择 | 判断选择的本地资源是否上传到错误的存储桶 |
二、日志数据的文件保存、滚动存储
1、数据的文件保存
写入xlog文件中。
2、数据的文件滚动存储
2.1、日志的过期清理
1、本地日志文件结构
-- 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、加密:
写入日志字符串时候,对编码后的字节数据,额外增加一个三位随机值。
/// 使用 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文件进行解密
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、日志文件命名及目录规范
以用户1000001
在2020-01
时候上传声音文件 beautiful_scenery.aac
为例,其路径完整示例如下:
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 | 文件名 |
{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 或 空值 |
接口名定义:
function getLoggerConfig(userid, deviceId)
接口结果的json示例如下:
{
"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 或 空值 |
接口名定义:
function getLoggerSalvageAndRevert(salvageUserids, salvageDeviceIds)
接口结果的json示例如下:
{
"salvageId": "1001",
"salvageStartDate": "2020-01-01",
"salvageEndDate": "2020-01-07",
"removeLogBeforeDate": "2020-12-31"
}
3、日志文件上传到cos后同步给后台
用途:日志文件上传到cos后同步给后台,后台数据库记录每个用户,每个设备都捞到了什么数据。
接口名定义:
function addLoggerFile(userid, deviceId)
接口结果的json示例如下:
{
}
四、日志回捞
1、后台通过接口返回要回捞的信息。
2、前端用户在下次使用,收到后台信息时候,进行本地日志的上传。在腾云存储桶能收到对应日志,即代表成功。
五、方案实施
灰度方案:请参照 灰度系统 。(附:日志记录开关里目前已有灰度策略。)
目的:避免功能异常,出现集体性问题。