GetPatternsAnalyzer
sql
# 定义一个叫做t0的临时表格
with t0 as (
# 从原始日志表log中选择spanName和ServiceName两个字段
select spanName, serviceName,
#
# JSON_EXTRACT_SCALAR 是 SPL 内置函数,用于从 JSON 字符串中提取标量值
# 路径 '$["k8s.pod.ip"]' 表示访问 JSON 对象中键为 "k8s.pod.ip" 的值
# 提取结果命名为 pod_ip
JSON_EXTRACT_SCALAR(resources, '$["k8s.pod.ip"]') AS pod_ip,
JSON_EXTRACT_SCALAR(resources, '$["k8s.node.name"]') AS node_name,
JSON_EXTRACT_SCALAR(resources, '$["service.version"]') AS service_version,
# 设置异常标签
if((statusCode = 2 or statusCode = 3), 'true', 'false') as anomaly_label,
# 使用if判断 statusCode 是否为 2 或 3。如果是,返回 1;否则返回 0。
# 然后使用 cast(... as double) 将整数转为双精度浮点数。
# 实际返回是否出错的数值表示 0或者1
cast(if((statusCode = 2 or statusCode = 3), 1, 0) as double) as error_count
from log
# 设置占位符 实际使用时将被替换成具体的执行添加
# 在当前案例中的是上一个阶段生成的根因span条件
where {self.condition}
),
假设输出的t0结构如此
spanName | serviceName | pod_ip | node_name | service_version | anomaly_label | error_count |
---|---|---|---|---|---|---|
"/login" | "auth-service" | "10.1.2.3" | "node-a" | "v1.2.0" | "false" | 0.0 |
"/getProfile" | "user-service" | "10.1.2.4" | "node-b" | "v2.0.1" | "true" | 1.0 |
"/login" | "auth-service" | "10.1.2.5" | "node-a" | "v1.2.0" | "false" | 0.0 |
sql
# array_agg(column) 是 SPL(以及标准 SQL)中的聚合函数,用于将某一列的所有值收集到一个数组(list)中。
t1 as (
select array_agg(spanName) as spanName,
array_agg(serviceName) as serviceName,
array_agg(pod_ip) as pod_ip,
array_agg(node_name) as node_name,
array_agg(service_version) as service_version,
array_agg(anomaly_label) as anomaly_label,
array_agg(error_count) as error_count
from t0
),
# 执行后,t1 只有一行,包含 7 个字段,每个字段都是一个数组,数组长度等于 t0 的行数
# 目前来看 t1保留了很多的字段 但是只使用了spanName 和 serviceName 可能是为后续进行了一些保留
json
# t1
{
"spanName": ["/login", "/getProfile", "/login"],
"serviceName": ["auth-service", "user-service", "auth-service"],
"pod_ip": ["10.1.2.3", "10.1.2.4", "10.1.2.5"],
"node_name": ["node-a", "node-b", "node-a"],
"service_version": ["v1.2.0", "v2.0.1", "v1.2.0"],
"anomaly_label": ["false", "true", "false"],
"error_count": [0.0, 1.0, 0.0]
}
sql
# 将 t1 中的 spanName 和 serviceName 两个数组字段封装成一个结构化的行对象,便于作为整体传递给后续处理逻辑(如自定义函数),强调这两个字段的逻辑关联性。
t2 as (
# row( ) 是一个用于构造结构化复合数据类型的函数,它返回一个 ROW 类型(也称为"行类型"或"结构体")的对象。
select row(spanName, serviceName) as table_row
from t1
),
# 返回一个 ROW(ARRAY<STRING>, ARRAY<STRING>) 类型的值
# 一个包含两个元素的结构体;第一个元素是 spanName 数组;第二个元素是 serviceName 数组
# 如果没有显式指定名称,SPL 会默认使用 col0, col1, col2, ... 作为内部字段名。
# table_row.col0 → 对应 spanName
# table_row.col1 → 对应 serviceName
json
# t2
{
"table_row": {
"col0": ["/login", "/getProfile", "/login"],
"col1": ["auth-service", "user-service", "auth-service"]
}
}
sql
# get_patterns用于从 trace 数据中提取高频或异常的调用序列模式
# table_row是上面的数组 ARRAY['spanName', 'serviceName'] 指明 col0 代表 spanName col1 代表 serviceName
t3 as (
select get_patterns(table_row, ARRAY['spanName', 'serviceName']) as ret
from t2
)
select * from t3
# 该函数将统计所有(spanName,serviceName)的组合
json
# 某题实际的响应内容如下
{'ret': '[["serviceName=payment"],[118],null,null]'}
DiffPatternsAnalyzer
sql
# 进行原始日志过滤以及字段的提取 大部分内容同上
with t0 as (
select
spanName,
serviceName,
JSON_EXTRACT_SCALAR(resources, '$["k8s.pod.ip"]') AS pod_ip,
JSON_EXTRACT_SCALAR(resources, '$["k8s.node.name"]') AS node_name,
JSON_EXTRACT_SCALAR(resources, '$["service.version"]') AS service_version,
# 根据上面给出的根因span进行标记
if(({self.condition}), 'true', 'false') as anomaly_label,
cast(if((statusCode = 2 or statusCode = 3), 1, 0) as double) as error_count
from log
where statusCode > 0
)
sql
# 将 t0 中所有行的每个字段聚合成一个数组 同上
t1 as (
select
array_agg(spanName) as spanName,
array_agg(serviceName) as serviceName,
array_agg(pod_ip) as pod_ip,
array_agg(node_name) as node_name,
array_agg(service_version) as service_version,
array_agg(anomaly_label) as anomaly_label,
array_agg(error_count) as error_count
from t0
)
sql
# 只关心服务与是否异常的关联性 同上
t2 as (
select row(serviceName, anomaly_label) as table_row
from t1
)
sql
t3 as (
select diff_patterns(
table_row, -- 包含维度字段和标签字段的 ROW 对象
ARRAY['serviceName', 'anomaly_label'], -- 字段名映射数组
'anomaly_label', -- 用于分组的标签列,用于划分正常数组和异常数组
'true', -- 正样本标签值
'false' -- 负样本标签值
) as ret
from t2
)
该 diff_patterns
函数首先会对输入的所有记录按照异常标签(anomaly_label
)进行分组:将 anomaly_label = 'true'
的记录划分为"异常组"(Group A),anomaly_label = 'false'
的记录划分为"正常组"(Group B),并分别提取两组中对应的 serviceName
值。
接着,函数会统计每个服务名称在异常组和正常组中的出现频次,例如某个服务如 payment-service
可能在异常组中出现了 200 次,而在正常组中仅出现 50 次,而另一个服务如 auth-service
可能在异常组中出现 50 次,却在正常组中出现 1000 次。
基于这些频次数据,函数进一步计算关键的差异指标,包括提升度(Lift,即异常组中该服务的占比除以正常组中的占比)、差异比例(Diff Ratio)以及统计显著性(如 p-value),用以量化每个服务与异常行为的关联强度。最后,函数会根据这些指标(通常是 Lift 或显著性)对服务进行排序,并返回最显著的差异模式列表,从而帮助用户快速识别出与异常高度相关的核心服务。
具体内容详见差异模式挖掘函数diff_patterns通过分析表格数据的维度和指标差异,识别测试组与对照组之间的显著模式,适用于云数据诊断与分析。
LogPatternAnalyzer
stage1
sql
# t1:聚合所有日志消息为一个数组
with t0 as (
select
statusCode, # 状态码
statusMessage,# 错误信息描述
serviceName,# 服务名
# 拼接出一个人工日志文本 如[serviceName] statusMessage
CONCAT('[', serviceName, '] ', statusMessage) as combined_message
from log
# 对根因节点列表中状态码大于0的部分进行检索
where {self.condition} and statusCode > 0
)
sql
# 聚合所有日志消息为一个数组
t1 as (
select array_agg(combined_message) as contents
from t0
)
# 模拟结果
[
"[auth-service] Invalid token",
"[user-service] User not found: id=123",
"[user-service] User not found: id=456",
...
]
sql
# 调用日志发现函数
t2 as (
select
contents,
get_log_patterns(
contents, -- 日志行数组
ARRAY[' ', '\\n', '\\t', '\\r', '\\f', '\\v', ':', ',', '[', ']'], -- 分词分隔符列表
null, -- 时间格式(null 表示不解析时间)
null, -- 正则过滤(null 表示不过滤)
'{"threshold": 3, "tolerance": 0.3, "maxDigitRatio": 0.3}'-- JSON 字符串,控制聚类参数
# "threshold": 3 最小支持度 一个模式至少出现 3 次才被视为有效模板
# "tolerance": 0.3 容错率 允许 30% 的 token 差异仍归为同一模式(用于模糊匹配)
# "maxDigitRatio": 0.3 若一行日志中数字字符占比 > 30%,可能被过滤或特殊处理(避免纯 ID 日志干扰)
) as ret
from t1
)
sql
# 提取所需的关键结果 只要模式id和对应的错误信息(可以去阿里云文档看看get_log_patterns的返回内容)
select
ret.model_id as model_id,
ret.error_msg as error_msg
from t2
stage2
sql
# 效果同上
with t0 as (
select CONCAT('[', serviceName, '] ', statusMessage) as combined_message
from log
where {self.condition} and statusCode > 0
)
sql
# 调用match_log_patterns
# 使用指定的模型(model_id)对单条日志(log_line)进行解析,判断是否匹配已知模式,并返回结构化结果。
select match_log_patterns('{model_id}', combined_message) as ret, combined_message
from t0
sql
# 外层循环 筛选并输出结构化结果
select
ret.is_matched as is_matched,
ret.pattern_id as pattern_id,
ret.pattern as pattern,
ret.regexp as pattern_regexp,
ret.variables as variables,
combined_message as content
from (
... -- 上述子查询
)
where ret.is_matched = true
limit 1000