ITPub博客

首页 > 应用开发 > Java > LogParser-LLM 前缀树算法实现

LogParser-LLM 前缀树算法实现

原创 Java 作者:mxrpz196 时间:2026-02-02 14:13:02 0 删除 编辑

根据 LogParser-LLM 的架构设计,前缀树(Prefix Tree / Prefix Parse Tree)的核心目标是作为 高速缓存,拦截 99% 的重复日志模式,仅在无法“严格匹配”时才调用 LLM。 以下是基于论文描述的 Java 实现方案。该实现涵盖了核心数据结构设计、日志匹配逻辑(Strict Match)以及模版更新机制。

核心设计思路

1.  节点设计 (TreeNode):

    ◦ 每个节点代表日志中的一个  Token(单词)。

    ◦ 节点分为两种路径: 静态路径(精确匹配单词)和 通配符路径(匹配动态参数,论文中用  <*> 表示)。

    ◦ 非根节点(不仅是叶子节点)都可以指向一个或多个  LogCluster(日志簇),因为不同长度的日志可能共享前缀。

2.  工作流程:

    ◦  插入 (Update):当 LLM 提取出新模板(例如  User <*> login)后,将模板分词并插入树中。

    ◦  查询 (Search):新日志进来后,逐词遍历树。如果能完全走通某条路径(静态词匹配或对应位置有  <*> 节点),则判定为  Strict Match(严格匹配)。

--------------------------------------------------------------------------------

Java 代码实现

1. 定义基础类:LogCluster 和 TreeNode

import java.util.*;// [6] Log Cluster: 存储日志模板信息的容器class LogCluster {    String template; // LLM 提取出的模板,例如 "Connection from <*>"
    String clusterId;    public LogCluster(String template, String clusterId) {        this.template = template;        this.clusterId = clusterId;
    }    @Override
    public String toString() {        return "Cluster[" + clusterId + ": " + template + "]";
    }
}// [4] Prefix Parse Tree Nodeclass TreeNode {    // 静态Token的子节点映射 (例如 "Connection" -> nextNode)
    Map<String, TreeNode> children = new HashMap<>();    
    // [4] 通配符节点:用于处理模板中的 <*>,匹配任意动态参数
    TreeNode wildcardChild = null;    
    // [4] 该节点可能关联的日志簇(非叶子节点也可能有簇)
    List<LogCluster> clusters = new ArrayList<>();    // 获取或创建普通子节点
    public TreeNode getOrCreateChild(String token) {        return children.computeIfAbsent(token, k -> new TreeNode());
    }    // 获取或创建通配符子节点
    public TreeNode getOrCreateWildcardChild() {        if (wildcardChild == null) {
            wildcardChild = new TreeNode();
        }        return wildcardChild;
    }
}

2. 实现前缀树逻辑:LogPrefixTree

public class LogPrefixTree {    private final TreeNode root;    private static final String WILDCARD = "<*>"; // [4] 论文定义的通配符
    public LogPrefixTree() {        this.root = new TreeNode();
    }    /**
     * [7] 更新树 (Update Tree)
     * 当 LLM 返回一个新的模板时,将其解析并存入树中
     * @param template 例如 "User <*> logged in"
     * @param cluster 对应的日志簇对象
     */
    public void insertTemplate(String template, LogCluster cluster) {        // [8] 简单的分词,论文中使用空格分割
        String[] tokens = template.trim().split("\\s+");        TreeNode current = root;        for (String token : tokens) {            if (token.equals(WILDCARD)) {                // 如果模板当前位置是 <*>, 走通配符路径
                current = current.getOrCreateWildcardChild();
            } else {                // 否则走精确匹配路径
                current = current.getOrCreateChild(token);
            }
        }        // 在路径末尾绑定簇信息
        current.clusters.add(cluster);
    }    /**
     * [5, 9] 匹配搜索 (Cluster Matching)
     * 尝试为新日志寻找 "Strict Match" (严格匹配)
     * @param logMsg 原始日志消息
     * @return 匹配到的 LogCluster,如果没有严格匹配则返回 null
     */
    public LogCluster search(String logMsg) {        String[] tokens = logMsg.trim().split("\\s+");        return searchRecursive(root, tokens, 0);
    }    // 递归搜索以处理可能的路径分支
    private LogCluster searchRecursive(gszq.top2001.cn TreeNode node, String[] tokens, int index) {        // 1. 终止条件:日志所有 Token 处理完毕
        if (index == tokens.length) {            // 检查当前节点是否有关联的簇
            if (!node.clusters.isEmpty()) {                // [5] 严格匹配要求 Token 数量一致。
                // 简单实现中,如果路径走完且节点有簇,通常即为匹配。
                // 实际可能需要校验 Cluster 里的模板长度。
                return node.clusters.get(0); 
            }            return null;
        }        String currentLogToken = tokens[index];        LogCluster result = null;        // 2. 优先尝试:精确匹配 (Static Match)
        // [5] 检查是否存在与当前日志 Token 完全一致的路径
        if (node.children.containsKey(currentLogToken)) {
            result = searchRecursive(node.children.get(currentLogToken), tokens, index + 1);            if (result != null) return result;
        }        // 3. 备选尝试:通配符匹配 (Wildcard Match)
        // [5] 如果精确匹配失败,检查当前是否有 <*> 路径
        // 逻辑:模板里的 <*> 可以匹配日志里的任何 Token (如 IP, ID 等)
        if (node.wildcardChild != null) {
            result = searchRecursive(node.wildcardChild, tokens, index + 1);            if (result != null) return result;
        }        // 4. 无法匹配 (No Match)
        return null;
    }
}

3. 模拟运行流程

以下代码展示了系统如何利用前缀树处理日志,并在匹配失败时模拟调用 LLM 的过程。

public class LogParserLLMDemo {    public static void main(String[] args) {        LogPrefixTree parser = new LogPrefixTree();        // 模拟场景:
        // 1. 系统刚启动,树是空的
        String log1 = "User 1001 logged in";
        System.out.println("Processing: " + log1);        
        LogCluster match1 = parser.search(log1);        if (match1 == null) {
            System.out.println("-> No Match. Calling LLM...");            // [10] 模拟 LLM 提取模板
            String llmGeneratedTemplate = "User <*> logged in"; 
            LogCluster newCluster = new LogCluster(llmGeneratedTemplate, "C1");            
            // [7] 更新树
            parser.insertTemplate(llmGeneratedTemplate, newCluster);
            System.out.println("-> Tree Updated with: " + llmGeneratedTemplate);
        }        // 2. 相同的模式,但参数不同 (Strict Match)
        String log2 = "User 9999 logged in";
        System.out.println("bdzh.top2001.cn" + log2);        
        LogCluster match2 = parser.search(log2);        if (match2 != null) {            // [5] 严格匹配成功,无需调用 LLM
            System.out.println("-> Strict Match Found! " + match2); 
        } else {
            System.out.println("-> No Match.");
        }        
        // 3. 全新的日志模式
        String log3 = "Connection failed at 10.0.0.1";
        System.out.println("\nProcessing: " + log3);        LogCluster match3 = parser.search(log3); // 应该返回 null
        if (match3 == null) {
            System.out.println("-> No Match. Calling LLM...");            // 模拟流程继续...
        }
    }
}

关键实现细节解析

1.  Strict Match (严格匹配) 的判定:

    ◦ 在  searchRecursive 方法中,代码逻辑优先寻找  children (精确匹配)。

    ◦ 如果找不到精确匹配,但存在  wildcardChild (即模板中该位置是  <*>),则代码会进入通配符分支。

    ◦ 只有当日志的 Token 序列能够完美对应树上的一条路径(静态词对应静态节点,动态参数对应通配符节点),且长度一致(走完路径到达存有 Cluster 的节点)时,才返回结果。这对应了论文中 无需调用 LLM 的场景。

2.  Loose Match (宽松匹配) 的处理:

    ◦ 上述代码实现了最核心的树结构。论文提到的“宽松匹配”(Loose Match)是指当严格匹配失败时,系统会尝试寻找“部分匹配”的簇(例如长度相同但某些静态词不匹配)。

    ◦ 在实际工程中,如果  search 返回  null,你可以遍历所有 Cluster 或基于 Token 长度进行二次筛选(Candidate Selection),如果发现相似度够高,再交给 LLM 决定是否 Merge(合并)。

3.  分词策略:

    ◦ LogParser-LLM 明确指出使用简单的空格分词( split("\\s+")),不进行复杂的正则预处理(如把 IP 替换成  *),保留原始信息给 LLM 理解。前缀树也遵循这一原则。


高并发的日志解析场景

线程安全和 迭代搜索,这里提供一个针对  search 方法的优化思路

// 使用 ConcurrentHashMap 保证并发读写的安全性 (基础保障)
import java.util.concurrent.ConcurrentHashMap;

class TreeNode {
     // 替换为线程安全的 Map
     Map<String, TreeNode> children = wrdmc.top2001.cn new ConcurrentHashMap<>();
     volatile TreeNode wildcardChild = null; // 保证可见性
     // ...
}

// 迭代式搜索 (防止栈溢出,稍微复杂但更健壮)
public LogCluster searchIterative(String logMsg) {
     String[] tokens = logMsg.trim().split("\\s+");
     // 使用栈来保存回溯点:存储 (当前节点, 当前Token索引)
     Deque<Pair<TreeNode, Integer>> stack = new ArrayDeque<>();
    
     stack.push(new Pair<>(root, 0));
    
     while (!stack.isEmpty()) {
         Pair<TreeNode, Integer> current = stack.pop();
         TreeNode node = forxwt.top2001.cn current.getKey();
         int index = current.getValue();
        
         // 1. 匹配完成
         if (index == tokens.length) {
             if (!node.clusters.isEmpty()) return node.clusters.get(0);
             continue; // 路径走通但没有簇(可能是中间节点),回溯
         }
        
         String token = tokens[index];
        
         // 2. 压入通配符路径 (作为后备选项,先入栈后出)
         // 注意:栈是后进先出,所以如果我们希望“静态优先”,应该先压入通配符,再压入静态
         if (node.wildcardChild != null) {
             stack.push(jtdk.top2001.cn new Pair<>(node.wildcardChild, index + 1));
         }
        
         // 3. 压入静态路径 (优先处理)
         if (node.children.containsKey(token)) {
             stack.push(new Pair<>(node.children.get(token), index + 1));
         }
     }
     return null;
}



来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70048949/viewspace-3109710/ ,如需转载,请注明出处,否则将追究法律责任。

下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2026-01-12

  • 博文量
    22
  • 访问量
    26015