如何用函数式F#处理含多行条目的debug/warning/info日志解析
日志解析的函数式F#实现需求
我正在逐行解析以debug、warning、info开头的日志文件,遇到了个别日志条目跨多行的情况,示例如下:
debug: Preparing movie /movies/menu_background.sfd: 272204 info: /init debug: Playing movie c:\program files (x86)\steam\steamapps\common\supreme commander forged alliance\movies\menu_background.sfd: 272204 warning: Error running OnDestroy script in Entity bsa0001 at 2f8b6908: ...gramdata\faforever\gamedata\lua.nx2\lua\sim\unit.lua(5494): attempt to call method `Destroy' (a nil value) stack traceback: ...gramdata\faforever\gamedata\lua.nx2\lua\sim\unit.lua(5494): in function <...gramdata\faforever\gamedata\lua.nx2\lua\sim\unit.lua:5489> ...orever\gamedata\lua.nx2\lua\sim\units\mobileunit.lua(65): in function `DestroyAllTrashBags'
当前我的F#代码如下,它会将这些额外行归入misclist,不符合需求。我希望修改代码,将额外行合并到对应的前序debug/warning/info条目中。我能用C#实现过程式方案,但希望以函数式风格完成,以此学习F#:
let rec sortLines (lines: string list) (warninglist: string list) (debuglist: string list) (infolist: string list) (misclist: string list) : string list list = match lines with | (h::t) when h.StartsWith("debug") -> sortLines t warninglist (h :: debuglist) infolist misclist | (h::t) when h.StartsWith("info") -> sortLines t warninglist debuglist (h :: infolist) misclist | (h::t) when h.StartsWith("warning") -> sortLines t (h :: warninglist) debuglist infolist misclist | (h::t) -> sortLines t warninglist debuglist infolist (h :: misclist) | [] -> [warninglist; debuglist; infolist; misclist]
函数式解决方案
步骤1:合并跨多行的日志条目
先把所有行合并为完整的日志条目:新行以debug:/info:/warning:开头时作为新条目,否则追加到上一个条目末尾。
type LogEntry = { Type: string; Content: string } let mergeMultilineLogs (lines: string list) = lines |> List.fold (fun (currentEntryOpt, completedEntries) line -> match line.TrimStart() with // 识别新日志条目的起始行 | l when l.StartsWith("debug:") -> // 先将未完成的条目加入已完成列表 let newCompleted = match currentEntryOpt with | Some entry -> entry :: completedEntries | None -> completedEntries // 开启新的debug条目 (Some { Type = "debug"; Content = l }, newCompleted) | l when l.StartsWith("info:") -> let newCompleted = match currentEntryOpt with | Some entry -> entry :: completedEntries | None -> completedEntries (Some { Type = "info"; Content = l }, newCompleted) | l when l.StartsWith("warning:") -> let newCompleted = match currentEntryOpt with | Some entry -> entry :: completedEntries | None -> completedEntries (Some { Type = "warning"; Content = l }, newCompleted) // 非起始行,追加到当前条目末尾 | l -> match currentEntryOpt with | Some current -> let updatedContent = current.Content + "\n" + line (Some { current with Content = updatedContent }, completedEntries) // 处理日志开头就是非起始行的情况,作为misc条目 | None -> (Some { Type = "misc"; Content = l }, completedEntries) ) (None, []) // 处理最后一个未完成的条目 |> fun (lastEntryOpt, completed) -> match lastEntryOpt with | Some entry -> entry :: completed | None -> completed // 反转列表恢复原始日志顺序 |> List.rev
步骤2:按类型分类条目
把合并后的完整条目分类到对应列表中:
let sortLogEntries (entries: LogEntry list) = entries |> List.fold (fun (warnings, debugs, infos, miscs) entry -> match entry.Type with | "warning" -> (entry.Content :: warnings, debugs, infos, miscs) | "debug" -> (warnings, entry.Content :: debugs, infos, miscs) | "info" -> (warnings, debugs, entry.Content :: infos, miscs) | _ -> (warnings, debugs, infos, entry.Content :: miscs) ) ([], [], [], []) // 反转每个列表恢复原始顺序 |> fun (w, d, i, m) -> [List.rev w; List.rev d; List.rev i; List.rev m]
组合使用
将两个函数结合,即可得到符合需求的处理结果:
let processLogs lines = lines |> mergeMultilineLogs |> sortLogEntries
代码说明
mergeMultilineLogs:通过List.fold维护状态,跟踪当前正在构建的日志条目和已完成的条目列表,全程无可变状态,纯函数式处理跨多行合并逻辑。sortLogEntries:再次使用List.fold完成分类,最后反转列表保证输出顺序与原始日志一致。- 整个流程完全遵循函数式编程范式,避免了过程式代码中的可变变量,贴合F#的语言特性。
内容的提问来源于stack exchange,提问作者user426445
相关产品推荐
相关产品推荐

