From 0e20692d341371daddc328c0c137885f4db055e7 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 00:56:07 +0000 Subject: [PATCH 1/5] [log2src] use the log levels of statements log2src now gives each statement the level of its logging call. The message's level is passed along when looking for its statement, so a statement with the same level wins a tie, and source_log_stmt() has a new level column. lnav-rs-ext points at the log2src branch that brings the levels into caching1 until that is merged. The expected output for the patterns that now allow empty values, from the previous log2src update, is updated too. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KTECZJ1AReK76iN7tgD1KY --- NEWS.md | 7 +++ src/all_logs_vtab.cc | 10 +++- src/all_logs_vtab.hh | 4 +- src/log_data_helper.cc | 8 ++- src/log_stmt_stats.cc | 8 +-- src/log_stmt_vtab.cc | 13 +++++ src/third-party/lnav-rs-ext/Cargo.lock | 12 ++--- src/third-party/lnav-rs-ext/Cargo.toml | 2 +- .../lnav-rs-ext/lnav_rs_ext.cxx.cc | 15 +++--- .../lnav-rs-ext/lnav_rs_ext.cxx.hh | 7 ++- src/third-party/lnav-rs-ext/src/lib.rs | 51 +++++++++++++++++-- ...a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out | 3 ++ ...9d47fea76d38d62a65f8c4f3ac9d45d3dd2997.out | 6 +-- ...143666863b534b0dccb8c3122919752929908f.out | 4 -- ...fad25dfbecb3e452e744c6bb0999d28f5da258.out | 2 +- ...839112be395d89ad262e05b3e69493ea371ed.err} | 0 ...7839112be395d89ad262e05b3e69493ea371ed.out | 4 ++ ...6a0e9c1aa2630c9af568d27ffa0eff2db85a8.err} | 0 ...96a0e9c1aa2630c9af568d27ffa0eff2db85a8.out | 5 ++ ...b818f636cb8557d4f651fa4b548e502a50aece.out | 5 -- test/test_log2src.sh | 4 +- 21 files changed, 128 insertions(+), 42 deletions(-) delete mode 100644 test/expected/test_log2src.sh_0c143666863b534b0dccb8c3122919752929908f.out rename test/expected/{test_log2src.sh_0c143666863b534b0dccb8c3122919752929908f.err => test_log2src.sh_bf7839112be395d89ad262e05b3e69493ea371ed.err} (100%) create mode 100644 test/expected/test_log2src.sh_bf7839112be395d89ad262e05b3e69493ea371ed.out rename test/expected/{test_log2src.sh_fdb818f636cb8557d4f651fa4b548e502a50aece.err => test_log2src.sh_ef96a0e9c1aa2630c9af568d27ffa0eff2db85a8.err} (100%) create mode 100644 test/expected/test_log2src.sh_ef96a0e9c1aa2630c9af568d27ffa0eff2db85a8.out delete mode 100644 test/expected/test_log2src.sh_fdb818f636cb8557d4f651fa4b548e502a50aece.out diff --git a/NEWS.md b/NEWS.md index a2c838e10ca..945bf561cd7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -322,6 +322,13 @@ Features: `schema` column with the `log_msg_schema` of the messages logged by each statement, so they can be found with a `log_msg_schema = …` constraint. +* The `source_log_stmt()` table-valued function has a new + `level` column with the level of the messages logged by + each statement, as log2src determined it from the logging + call (e.g. `warning` for `log.warn(...)`), or NULL when it + could not tell, like for a `printf()`. When a message + matches more than one statement equally well, the one with + the same level as the message is now chosen. Interface Changes: * Moving horizontally now defaults to moving to the diff --git a/src/all_logs_vtab.cc b/src/all_logs_vtab.cc index 5c7fbca3ca6..98dd506084d 100644 --- a/src/all_logs_vtab.cc +++ b/src/all_logs_vtab.cc @@ -362,8 +362,11 @@ all_logs_vtab::extract(const log_cursor::extract_context& ec, #ifdef HAVE_RUST_DEPS auto find_res = unmatched ? nullptr - : find_statement( - line, sa, sub_values, ec.is_column_used(this->alv_values_meta)); + : find_statement(line, + sa, + sub_values, + lf->begin()[line_number].get_msg_level(), + ec.is_column_used(this->alv_values_meta)); if (find_res != nullptr) { char schema_buf[hasher::STRING_SIZE]; statement_schema(string_fragment::from(find_res->src), @@ -433,6 +436,7 @@ std::unique_ptr all_logs_vtab::find_statement(const shared_buffer_ref& sbr, const string_attrs_t& sa, const logline_value_vector& values, + log_level_t level, bool extract_variables) { if (!sbr.get_metadata().m_valid_utf) { @@ -463,9 +467,11 @@ all_logs_vtab::find_statement(const shared_buffer_ref& sbr, auto find_options = lnav_rs_ext::FindLogOptions{ .extract_variables = extract_variables, }; + const auto& level_name = level_names[level]; return lnav_rs_ext::find_log_statement_json( file_rust_str, lineno, + rust::Str(level_name.data(), level_name.length()), rust::Str(body_sf.data(), body_sf.length()), find_options); } diff --git a/src/all_logs_vtab.hh b/src/all_logs_vtab.hh index e1096b9e4c5..620cd3e5be9 100644 --- a/src/all_logs_vtab.hh +++ b/src/all_logs_vtab.hh @@ -139,12 +139,14 @@ public: /** * Finds the source statement that logged a message, or nullptr. The * sbr is the message, sa and values are what the format's annotate() - * gave for it. + * gave for it, and level is the message's level, which breaks ties + * between statements that match equally well. */ static std::unique_ptr find_statement( const shared_buffer_ref& sbr, const string_attrs_t& sa, const logline_value_vector& values, + log_level_t level, bool extract_variables); #endif diff --git a/src/log_data_helper.cc b/src/log_data_helper.cc index f5eefa9bc74..771a24b386d 100644 --- a/src/log_data_helper.cc +++ b/src/log_data_helper.cc @@ -268,8 +268,12 @@ log_data_helper::parse_body() } this->ldh_src_ref = std::nullopt; this->ldh_src_vars.clear(); - auto find_res - = lnav_rs_ext::find_log_statement(file_rust_str, lineno, body_rust_str); + const auto& level_name = level_names[this->ldh_line->get_msg_level()]; + auto find_res = lnav_rs_ext::find_log_statement( + file_rust_str, + lineno, + rust::Str(level_name.data(), level_name.length()), + body_rust_str); if (find_res != nullptr) { this->ldh_src_ref = lnav::src_ref{ std::filesystem::path((std::string) find_res->src.file), diff --git a/src/log_stmt_stats.cc b/src/log_stmt_stats.cc index 567338b4324..f4a227b1464 100644 --- a/src/log_stmt_stats.cc +++ b/src/log_stmt_stats.cc @@ -311,6 +311,7 @@ struct stmt_map { const shared_buffer_ref& sbr, const string_attrs_t& sa, const logline_value_vector& values, + log_level_t level, match_scratch& ms) { #ifdef HAVE_RUST_DEPS @@ -325,7 +326,7 @@ struct stmt_map { } } - auto res = all_logs_vtab::find_statement(sbr, sa, values, true); + auto res = all_logs_vtab::find_statement(sbr, sa, values, level, true); if (res == nullptr) { return; } @@ -370,14 +371,15 @@ struct stmt_map { format->annotate(lf, line_number, sa, values); - auto find_res = all_logs_vtab::find_statement(sbr, sa, values, false); + auto find_res = all_logs_vtab::find_statement( + sbr, sa, values, ll->get_msg_level(), false); if (find_res == nullptr) { return NO_STATEMENT; } auto id = this->intern_cached( ms.ms_cache, ms.ms_arena, find_res->src, find_res->pattern); - this->sample_vars(id, sbr, sa, values, ms); + this->sample_vars(id, sbr, sa, values, ll->get_msg_level(), ms); return id; #else return NO_STATEMENT; diff --git a/src/log_stmt_vtab.cc b/src/log_stmt_vtab.cc index e25e4871231..f1f7d1c00e4 100644 --- a/src/log_stmt_vtab.cc +++ b/src/log_stmt_vtab.cc @@ -48,6 +48,7 @@ enum class log_stmt_col : uint8_t { function_name, qualified_name, block_id, + level, pattern, path, schema, @@ -67,6 +68,7 @@ CREATE TABLE lnav_db.source_log_stmt ( function_name TEXT, qualified_name TEXT, block_id INTEGER, + level TEXT, pattern TEXT, path TEXT, schema TEXT, @@ -132,6 +134,13 @@ CREATE TABLE lnav_db.source_log_stmt ( case log_stmt_col::block_id: to_sqlite(ctx, (int64_t) stmt.src.block_id); break; + case log_stmt_col::level: + if (stmt.level.empty()) { + sqlite3_result_null(ctx); + } else { + to_sqlite(ctx, (std::string) stmt.level); + } + break; case log_stmt_col::pattern: to_sqlite(ctx, (std::string) stmt.pattern); break; @@ -247,6 +256,10 @@ register_log_stmt_vtab(sqlite3* db) "An ID for the innermost block containing the " "statement. Statements in the same file with the " "same ID are in the same block."}) + .with_result({"level", + "The level of the messages logged by this " + "statement, or NULL if it could not be " + "determined, like for a printf()"}) .with_result({"pattern", "The pattern used to match log messages from this " "log statement"}) diff --git a/src/third-party/lnav-rs-ext/Cargo.lock b/src/third-party/lnav-rs-ext/Cargo.lock index 43249457f42..2e275763073 100644 --- a/src/third-party/lnav-rs-ext/Cargo.lock +++ b/src/third-party/lnav-rs-ext/Cargo.lock @@ -799,7 +799,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1258,7 +1258,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1396,7 +1396,7 @@ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "log2src" version = "0.1.0" -source = "git+https://github.com/tstack/log2src.git?branch=caching1#c654a6151bdee4d6d38474fbe76503521d6b19dc" +source = "git+https://github.com/tstack/log2src.git?branch=claude/project-thread-dj3xkq#98c865eb4cb8c3e6eb51d6a119087f5547119b13" dependencies = [ "aho-corasick", "bincode", @@ -1905,7 +1905,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2222,7 +2222,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2706,7 +2706,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/src/third-party/lnav-rs-ext/Cargo.toml b/src/third-party/lnav-rs-ext/Cargo.toml index 4070ddca0ff..f04e676c141 100644 --- a/src/third-party/lnav-rs-ext/Cargo.toml +++ b/src/third-party/lnav-rs-ext/Cargo.toml @@ -27,7 +27,7 @@ prqlc = "0.13.10" cxx = "1.0" log = "0.4" miette = "7.6.0" -log2src = {git = "https://github.com/tstack/log2src.git", branch = "caching1"} +log2src = {git = "https://github.com/tstack/log2src.git", branch = "claude/project-thread-dj3xkq"} # log2src = {path = "/Users/tstack/github/log2src"} rouille = "3.6.2" cookie = "0.18.1" diff --git a/src/third-party/lnav-rs-ext/lnav_rs_ext.cxx.cc b/src/third-party/lnav-rs-ext/lnav_rs_ext.cxx.cc index 2e508606e7e..1b55653efb3 100644 --- a/src/third-party/lnav-rs-ext/lnav_rs_ext.cxx.cc +++ b/src/third-party/lnav-rs-ext/lnav_rs_ext.cxx.cc @@ -999,6 +999,9 @@ struct FindLogResult final { // `src` as `find_log_statement_json()` gives it, which the schema hashes. ::rust::String src_json; ::rust::String pattern; + // The statement's level, as lnav names it, or empty if log2src + // could not tell, like for a `printf()`. + ::rust::String level; ::rust::Vec<::lnav_rs_ext::VarPair> variables; using IsRelocatable = ::std::true_type; @@ -1190,9 +1193,9 @@ void lnav_rs_ext$cxxbridge1$205$discover_srcs() noexcept; void lnav_rs_ext$cxxbridge1$205$get_status(::lnav_rs_ext::ExtProgress *return$) noexcept; -::lnav_rs_ext::FindLogResult *lnav_rs_ext$cxxbridge1$205$find_log_statement(::rust::Str file, ::std::size_t line, ::rust::Str body) noexcept; +::lnav_rs_ext::FindLogResult *lnav_rs_ext$cxxbridge1$205$find_log_statement(::rust::Str file, ::std::size_t line, ::rust::Str level, ::rust::Str body) noexcept; -::lnav_rs_ext::FindLogResultJson *lnav_rs_ext$cxxbridge1$205$find_log_statement_json(::rust::Str file, ::std::size_t line, ::rust::Str body, ::lnav_rs_ext::FindLogOptions const &options) noexcept; +::lnav_rs_ext::FindLogResultJson *lnav_rs_ext$cxxbridge1$205$find_log_statement_json(::rust::Str file, ::std::size_t line, ::rust::Str level, ::rust::Str body, ::lnav_rs_ext::FindLogOptions const &options) noexcept; void lnav_rs_ext$cxxbridge1$205$get_log_statements_for(::rust::Str file, ::rust::Vec<::lnav_rs_ext::FindLogResult> *return$) noexcept; @@ -1229,12 +1232,12 @@ ::lnav_rs_ext::ExtProgress get_status() noexcept { return ::std::move(return$.value); } -::std::unique_ptr<::lnav_rs_ext::FindLogResult> find_log_statement(::rust::Str file, ::std::size_t line, ::rust::Str body) noexcept { - return ::std::unique_ptr<::lnav_rs_ext::FindLogResult>(lnav_rs_ext$cxxbridge1$205$find_log_statement(file, line, body)); +::std::unique_ptr<::lnav_rs_ext::FindLogResult> find_log_statement(::rust::Str file, ::std::size_t line, ::rust::Str level, ::rust::Str body) noexcept { + return ::std::unique_ptr<::lnav_rs_ext::FindLogResult>(lnav_rs_ext$cxxbridge1$205$find_log_statement(file, line, level, body)); } -::std::unique_ptr<::lnav_rs_ext::FindLogResultJson> find_log_statement_json(::rust::Str file, ::std::size_t line, ::rust::Str body, ::lnav_rs_ext::FindLogOptions const &options) noexcept { - return ::std::unique_ptr<::lnav_rs_ext::FindLogResultJson>(lnav_rs_ext$cxxbridge1$205$find_log_statement_json(file, line, body, options)); +::std::unique_ptr<::lnav_rs_ext::FindLogResultJson> find_log_statement_json(::rust::Str file, ::std::size_t line, ::rust::Str level, ::rust::Str body, ::lnav_rs_ext::FindLogOptions const &options) noexcept { + return ::std::unique_ptr<::lnav_rs_ext::FindLogResultJson>(lnav_rs_ext$cxxbridge1$205$find_log_statement_json(file, line, level, body, options)); } ::rust::Vec<::lnav_rs_ext::FindLogResult> get_log_statements_for(::rust::Str file) noexcept { diff --git a/src/third-party/lnav-rs-ext/lnav_rs_ext.cxx.hh b/src/third-party/lnav-rs-ext/lnav_rs_ext.cxx.hh index 764c7bd0de9..8af7bfea618 100644 --- a/src/third-party/lnav-rs-ext/lnav_rs_ext.cxx.hh +++ b/src/third-party/lnav-rs-ext/lnav_rs_ext.cxx.hh @@ -963,6 +963,9 @@ struct FindLogResult final { // `src` as `find_log_statement_json()` gives it, which the schema hashes. ::rust::String src_json; ::rust::String pattern; + // The statement's level, as lnav names it, or empty if log2src + // could not tell, like for a `printf()`. + ::rust::String level; ::rust::Vec<::lnav_rs_ext::VarPair> variables; using IsRelocatable = ::std::true_type; @@ -1113,9 +1116,9 @@ void discover_srcs() noexcept; ::lnav_rs_ext::ExtProgress get_status() noexcept; -::std::unique_ptr<::lnav_rs_ext::FindLogResult> find_log_statement(::rust::Str file, ::std::size_t line, ::rust::Str body) noexcept; +::std::unique_ptr<::lnav_rs_ext::FindLogResult> find_log_statement(::rust::Str file, ::std::size_t line, ::rust::Str level, ::rust::Str body) noexcept; -::std::unique_ptr<::lnav_rs_ext::FindLogResultJson> find_log_statement_json(::rust::Str file, ::std::size_t line, ::rust::Str body, ::lnav_rs_ext::FindLogOptions const &options) noexcept; +::std::unique_ptr<::lnav_rs_ext::FindLogResultJson> find_log_statement_json(::rust::Str file, ::std::size_t line, ::rust::Str level, ::rust::Str body, ::lnav_rs_ext::FindLogOptions const &options) noexcept; ::rust::Vec<::lnav_rs_ext::FindLogResult> get_log_statements_for(::rust::Str file) noexcept; diff --git a/src/third-party/lnav-rs-ext/src/lib.rs b/src/third-party/lnav-rs-ext/src/lib.rs index 560dbe3317a..c650c0798c8 100644 --- a/src/third-party/lnav-rs-ext/src/lib.rs +++ b/src/third-party/lnav-rs-ext/src/lib.rs @@ -11,8 +11,8 @@ use crate::ffi::{ use cxx::UniquePtr; use log::{Level, Log, Metadata, Record}; use log2src::{ - Cache, LogError, LogMapping, LogMatchOptions, LogMatcher, LogRefBuilder, ProgressTracker, - ProgressUpdate, SourceRef, VariablePair, WorkInfo, + Cache, LogError, LogLevel, LogMapping, LogMatchOptions, LogMatcher, LogRefBuilder, + ProgressTracker, ProgressUpdate, SourceRef, VariablePair, WorkInfo, }; use miette::Diagnostic; use prqlc::{DisplayOptions, Target}; @@ -234,6 +234,9 @@ mod ffi { /// `src` as `find_log_statement_json()` gives it, which the schema hashes. pub src_json: String, pub pattern: String, + /// The statement's level, as lnav names it, or empty if log2src + /// could not tell, like for a `printf()`. + pub level: String, pub variables: Vec, } @@ -369,10 +372,16 @@ mod ffi { fn get_status() -> ExtProgress; - fn find_log_statement(file: &str, line: usize, body: &str) -> UniquePtr; + fn find_log_statement( + file: &str, + line: usize, + level: &str, + body: &str, + ) -> UniquePtr; fn find_log_statement_json( file: &str, line: usize, + level: &str, body: &str, options: &FindLogOptions, ) -> UniquePtr; @@ -584,6 +593,7 @@ impl From<&SourceRef> for FindLogResult { }, src_json: src_json(value), pattern: value.pattern_str.to_string(), + level: lnav_level_name(value.level).to_string(), variables: vec![], } } @@ -599,11 +609,42 @@ impl FindLogResult { } } -fn find_log_statement(file: &str, lineno: usize, body: &str) -> UniquePtr { +/// The name lnav uses for a statement's level. +fn lnav_level_name(level: LogLevel) -> &'static str { + match level { + LogLevel::Trace => "trace", + LogLevel::Debug => "debug", + LogLevel::Info => "info", + LogLevel::Warn => "warning", + LogLevel::Error => "error", + LogLevel::Fatal => "fatal", + LogLevel::Unknown => "", + } +} + +/// The level of a message, as lnav names it, in the form log2src parses, so +/// that statements with the same level are preferred. The levels that +/// log2src doesn't have are folded into the nearest one it does. +fn log2src_level_text(level: &str) -> Option<&str> { + match level { + "" | "unknown" | "invalid" | "header" => None, + "stats" => Some("info"), + _ if level.starts_with("debug") => Some("debug"), + _ => Some(level), + } +} + +fn find_log_statement( + file: &str, + lineno: usize, + level: &str, + body: &str, +) -> UniquePtr { let log_matcher = LOG_MATCHER.read().unwrap(); let log_ref = LogRefBuilder::new() .with_file(if file.is_empty() { None } else { Some(file) }) .with_lineno(if file.is_empty() { None } else { Some(lineno) }) + .with_level(log2src_level_text(level)) .with_body(Some(body)) .build(body); @@ -650,6 +691,7 @@ fn src_json(src_ref: &SourceRef) -> String { fn find_log_statement_json( file: &str, lineno: usize, + level: &str, body: &str, options: &FindLogOptions, ) -> UniquePtr { @@ -657,6 +699,7 @@ fn find_log_statement_json( let log_ref = LogRefBuilder::new() .with_file(if file.is_empty() { None } else { Some(file) }) .with_lineno(if file.is_empty() { None } else { Some(lineno) }) + .with_level(log2src_level_text(level)) .with_body(Some(body)) .build(body); diff --git a/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out b/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out index c0cc41330bc..e8358249d52 100644 --- a/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out +++ b/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out @@ -5360,6 +5360,9 @@ For support questions, email: block_id An ID for the innermost block containing the statement. Statements in the same file with the same ID are in the same block. + level The level of the messages logged + by this statement, or NULL if it could not be + determined, like for a printf() pattern The pattern used to match log messages from this log statement path The path to the source file diff --git a/test/expected/test_log2src.sh_039d47fea76d38d62a65f8c4f3ac9d45d3dd2997.out b/test/expected/test_log2src.sh_039d47fea76d38d62a65f8c4f3ac9d45d3dd2997.out index 8feb2ed70fd..120b18c90d3 100644 --- a/test/expected/test_log2src.sh_039d47fea76d38d62a65f8c4f3ac9d45d3dd2997.out +++ b/test/expected/test_log2src.sh_039d47fea76d38d62a65f8c4f3ac9d45d3dd2997.out @@ -1,4 +1,4 @@ -{"log_line":0,"log_msg_src":{"file":"{test_dir}/log2src/java/com/example/net/Server.java","begin_line":10,"end_line":10,"name":"Server","qualified_name":"com.example.net.Server.Server","block_id":215,"language":"Java"},"log_msg_format":"(?s)^constructing server on port (.+)$","log_msg_values":[{"expr":"port","value":"8080"}]} -{"log_line":1,"log_msg_src":{"file":"{test_dir}/log2src/java/com/example/net/Server.java","begin_line":14,"end_line":14,"name":"handle","qualified_name":"com.example.net.Server.handle","block_id":323,"language":"Java"},"log_msg_format":"(?s)^handling request from (.+)$","log_msg_values":[{"expr":"client","value":"10.0.0.1"}]} -{"log_line":2,"log_msg_src":{"file":"{test_dir}/log2src/java/com/example/net/Server.java","begin_line":15,"end_line":15,"name":"handle","qualified_name":"com.example.net.Server.handle","block_id":410,"language":"Java"},"log_msg_format":"(?s)^client (.+) is slow$","log_msg_values":[{"expr":"client","value":"10.0.0.1"}]} +{"log_line":0,"log_msg_src":{"file":"{test_dir}/log2src/java/com/example/net/Server.java","begin_line":10,"end_line":10,"name":"Server","qualified_name":"com.example.net.Server.Server","block_id":215,"language":"Java"},"log_msg_format":"(?s)^constructing server on port (.*)$","log_msg_values":[{"expr":"port","value":"8080"}]} +{"log_line":1,"log_msg_src":{"file":"{test_dir}/log2src/java/com/example/net/Server.java","begin_line":14,"end_line":14,"name":"handle","qualified_name":"com.example.net.Server.handle","block_id":323,"language":"Java"},"log_msg_format":"(?s)^handling request from (.*)$","log_msg_values":[{"expr":"client","value":"10.0.0.1"}]} +{"log_line":2,"log_msg_src":{"file":"{test_dir}/log2src/java/com/example/net/Server.java","begin_line":15,"end_line":15,"name":"handle","qualified_name":"com.example.net.Server.handle","block_id":410,"language":"Java"},"log_msg_format":"(?s)^client (.*) is slow$","log_msg_values":[{"expr":"client","value":"10.0.0.1"}]} {"log_line":3,"log_msg_src":{"file":"{test_dir}/log2src/java/com/example/net/Server.java","begin_line":21,"end_line":21,"name":"run","qualified_name":"com.example.net.Server.Inner.run","block_id":520,"language":"Java"},"log_msg_format":"(?s)^inner worker failed$","log_msg_values":[]} diff --git a/test/expected/test_log2src.sh_0c143666863b534b0dccb8c3122919752929908f.out b/test/expected/test_log2src.sh_0c143666863b534b0dccb8c3122919752929908f.out deleted file mode 100644 index cabc962e1ac..00000000000 --- a/test/expected/test_log2src.sh_0c143666863b534b0dccb8c3122919752929908f.out +++ /dev/null @@ -1,4 +0,0 @@ -{"begin_line":10,"end_line":10,"language":"Java","function_name":"Server","qualified_name":"com.example.net.Server.Server","block_id":215,"pattern":"(?s)^constructing server on port (.+)$","path":"{test_dir}/log2src/java/com/example/net/Server.java"} -{"begin_line":14,"end_line":14,"language":"Java","function_name":"handle","qualified_name":"com.example.net.Server.handle","block_id":323,"pattern":"(?s)^handling request from (.+)$","path":"{test_dir}/log2src/java/com/example/net/Server.java"} -{"begin_line":15,"end_line":15,"language":"Java","function_name":"handle","qualified_name":"com.example.net.Server.handle","block_id":410,"pattern":"(?s)^client (.+) is slow$","path":"{test_dir}/log2src/java/com/example/net/Server.java"} -{"begin_line":21,"end_line":21,"language":"Java","function_name":"run","qualified_name":"com.example.net.Server.Inner.run","block_id":520,"pattern":"(?s)^inner worker failed$","path":"{test_dir}/log2src/java/com/example/net/Server.java"} diff --git a/test/expected/test_log2src.sh_7afad25dfbecb3e452e744c6bb0999d28f5da258.out b/test/expected/test_log2src.sh_7afad25dfbecb3e452e744c6bb0999d28f5da258.out index 1a15898a7ad..e1a86587cb5 100644 --- a/test/expected/test_log2src.sh_7afad25dfbecb3e452e744c6bb0999d28f5da258.out +++ b/test/expected/test_log2src.sh_7afad25dfbecb3e452e744c6bb0999d28f5da258.out @@ -1,5 +1,5 @@ {"log_line":0,"log_msg_src":{"file":"{test_dir}/log2src/python/log_example.py","begin_line":19,"end_line":19,"name":"main","qualified_name":"main","block_id":407,"language":"Python"},"log_msg_format":"(?s)^debug message: initializing demo .$","log_msg_values":[]} -{"log_line":1,"log_msg_src":{"file":"{test_dir}/log2src/python/log_example.py","begin_line":20,"end_line":21,"name":"main","qualified_name":"main","block_id":407,"language":"Python"},"log_msg_format":"(?s)^info message:\\nprocessing started -- (.+)$","log_msg_values":[{"expr":"sys.argv[0]","value":"log_example.py"}]} +{"log_line":1,"log_msg_src":{"file":"{test_dir}/log2src/python/log_example.py","begin_line":20,"end_line":21,"name":"main","qualified_name":"main","block_id":407,"language":"Python"},"log_msg_format":"(?s)^info message:\\nprocessing started -- (.*)$","log_msg_values":[{"expr":"sys.argv[0]","value":"log_example.py"}]} {"log_line":3,"log_msg_src":{"file":"{test_dir}/log2src/python/log_example.py","begin_line":22,"end_line":22,"name":"main","qualified_name":"main","block_id":407,"language":"Python"},"log_msg_format":"(?s)^warning message:\\nlow disk space$","log_msg_values":[]} {"log_line":5,"log_msg_src":{"file":"{test_dir}/log2src/python/log_example.py","begin_line":23,"end_line":23,"name":"main","qualified_name":"main","block_id":407,"language":"Python"},"log_msg_format":"(?s)^error message: failed to open resource$","log_msg_values":[]} {"log_line":6,"log_msg_src":{"file":"{test_dir}/log2src/python/log_example.py","begin_line":27,"end_line":27,"name":"main","qualified_name":"main","block_id":802,"language":"Python"},"log_msg_format":"(?s)^exception message: caught unexpected error$","log_msg_values":[]} diff --git a/test/expected/test_log2src.sh_0c143666863b534b0dccb8c3122919752929908f.err b/test/expected/test_log2src.sh_bf7839112be395d89ad262e05b3e69493ea371ed.err similarity index 100% rename from test/expected/test_log2src.sh_0c143666863b534b0dccb8c3122919752929908f.err rename to test/expected/test_log2src.sh_bf7839112be395d89ad262e05b3e69493ea371ed.err diff --git a/test/expected/test_log2src.sh_bf7839112be395d89ad262e05b3e69493ea371ed.out b/test/expected/test_log2src.sh_bf7839112be395d89ad262e05b3e69493ea371ed.out new file mode 100644 index 00000000000..866595b925f --- /dev/null +++ b/test/expected/test_log2src.sh_bf7839112be395d89ad262e05b3e69493ea371ed.out @@ -0,0 +1,4 @@ +{"begin_line":10,"end_line":10,"language":"Java","function_name":"Server","qualified_name":"com.example.net.Server.Server","block_id":215,"level":"info","pattern":"(?s)^constructing server on port (.*)$","path":"{test_dir}/log2src/java/com/example/net/Server.java"} +{"begin_line":14,"end_line":14,"language":"Java","function_name":"handle","qualified_name":"com.example.net.Server.handle","block_id":323,"level":"debug","pattern":"(?s)^handling request from (.*)$","path":"{test_dir}/log2src/java/com/example/net/Server.java"} +{"begin_line":15,"end_line":15,"language":"Java","function_name":"handle","qualified_name":"com.example.net.Server.handle","block_id":410,"level":"warning","pattern":"(?s)^client (.*) is slow$","path":"{test_dir}/log2src/java/com/example/net/Server.java"} +{"begin_line":21,"end_line":21,"language":"Java","function_name":"run","qualified_name":"com.example.net.Server.Inner.run","block_id":520,"level":"error","pattern":"(?s)^inner worker failed$","path":"{test_dir}/log2src/java/com/example/net/Server.java"} diff --git a/test/expected/test_log2src.sh_fdb818f636cb8557d4f651fa4b548e502a50aece.err b/test/expected/test_log2src.sh_ef96a0e9c1aa2630c9af568d27ffa0eff2db85a8.err similarity index 100% rename from test/expected/test_log2src.sh_fdb818f636cb8557d4f651fa4b548e502a50aece.err rename to test/expected/test_log2src.sh_ef96a0e9c1aa2630c9af568d27ffa0eff2db85a8.err diff --git a/test/expected/test_log2src.sh_ef96a0e9c1aa2630c9af568d27ffa0eff2db85a8.out b/test/expected/test_log2src.sh_ef96a0e9c1aa2630c9af568d27ffa0eff2db85a8.out new file mode 100644 index 00000000000..8bcf0f86494 --- /dev/null +++ b/test/expected/test_log2src.sh_ef96a0e9c1aa2630c9af568d27ffa0eff2db85a8.out @@ -0,0 +1,5 @@ +{"begin_line":19,"end_line":19,"language":"Python","function_name":"main","qualified_name":"main","block_id":407,"level":"debug","pattern":"(?s)^debug message: initializing demo .$","path":"{test_dir}/log2src/python/log_example.py"} +{"begin_line":20,"end_line":21,"language":"Python","function_name":"main","qualified_name":"main","block_id":407,"level":"info","pattern":"(?s)^info message:\\nprocessing started -- (.*)$","path":"{test_dir}/log2src/python/log_example.py"} +{"begin_line":22,"end_line":22,"language":"Python","function_name":"main","qualified_name":"main","block_id":407,"level":"warning","pattern":"(?s)^warning message:\\nlow disk space$","path":"{test_dir}/log2src/python/log_example.py"} +{"begin_line":23,"end_line":23,"language":"Python","function_name":"main","qualified_name":"main","block_id":407,"level":"error","pattern":"(?s)^error message: failed to open resource$","path":"{test_dir}/log2src/python/log_example.py"} +{"begin_line":27,"end_line":27,"language":"Python","function_name":"main","qualified_name":"main","block_id":802,"level":"error","pattern":"(?s)^exception message: caught unexpected error$","path":"{test_dir}/log2src/python/log_example.py"} diff --git a/test/expected/test_log2src.sh_fdb818f636cb8557d4f651fa4b548e502a50aece.out b/test/expected/test_log2src.sh_fdb818f636cb8557d4f651fa4b548e502a50aece.out deleted file mode 100644 index d853503090f..00000000000 --- a/test/expected/test_log2src.sh_fdb818f636cb8557d4f651fa4b548e502a50aece.out +++ /dev/null @@ -1,5 +0,0 @@ -{"begin_line":19,"end_line":19,"language":"Python","function_name":"main","qualified_name":"main","block_id":407,"pattern":"(?s)^debug message: initializing demo .$","path":"{test_dir}/log2src/python/log_example.py"} -{"begin_line":20,"end_line":21,"language":"Python","function_name":"main","qualified_name":"main","block_id":407,"pattern":"(?s)^info message:\\nprocessing started -- (.+)$","path":"{test_dir}/log2src/python/log_example.py"} -{"begin_line":22,"end_line":22,"language":"Python","function_name":"main","qualified_name":"main","block_id":407,"pattern":"(?s)^warning message:\\nlow disk space$","path":"{test_dir}/log2src/python/log_example.py"} -{"begin_line":23,"end_line":23,"language":"Python","function_name":"main","qualified_name":"main","block_id":407,"pattern":"(?s)^error message: failed to open resource$","path":"{test_dir}/log2src/python/log_example.py"} -{"begin_line":27,"end_line":27,"language":"Python","function_name":"main","qualified_name":"main","block_id":802,"pattern":"(?s)^exception message: caught unexpected error$","path":"{test_dir}/log2src/python/log_example.py"} diff --git a/test/test_log2src.sh b/test/test_log2src.sh index bc115c5f789..f6f2c45dbf5 100644 --- a/test/test_log2src.sh +++ b/test/test_log2src.sh @@ -23,7 +23,7 @@ run_cap_test ${lnav_test} -n \ # which differs from one checkout to another. run_cap_test ${lnav_test} -nN \ -c ":add-source-path ${test_dir}/log2src/python" \ - -c ";SELECT begin_line, end_line, language, function_name, qualified_name, block_id, pattern, path FROM source_log_stmt('log_example.py')" \ + -c ";SELECT begin_line, end_line, language, function_name, qualified_name, block_id, level, pattern, path FROM source_log_stmt('log_example.py')" \ -c ":write-jsonlines-to -" run_cap_test ${lnav_test} -n \ @@ -34,7 +34,7 @@ run_cap_test ${lnav_test} -n \ run_cap_test ${lnav_test} -nN \ -c ":add-source-path ${test_dir}/log2src/java" \ - -c ";SELECT begin_line, end_line, language, function_name, qualified_name, block_id, pattern, path FROM source_log_stmt('Server.java')" \ + -c ";SELECT begin_line, end_line, language, function_name, qualified_name, block_id, level, pattern, path FROM source_log_stmt('Server.java')" \ -c ":write-jsonlines-to -" # log_stmt_stats counts the same messages per statement as grouping From 119b8d957a056a5cc3765f55ba9117b8f6182ba3 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 00:58:34 +0000 Subject: [PATCH 2/5] [log2src] go back to the caching1 branch now that the levels are in it Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KTECZJ1AReK76iN7tgD1KY --- src/third-party/lnav-rs-ext/Cargo.lock | 12 ++++++------ src/third-party/lnav-rs-ext/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/third-party/lnav-rs-ext/Cargo.lock b/src/third-party/lnav-rs-ext/Cargo.lock index 2e275763073..588eeeaa35d 100644 --- a/src/third-party/lnav-rs-ext/Cargo.lock +++ b/src/third-party/lnav-rs-ext/Cargo.lock @@ -799,7 +799,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1258,7 +1258,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1396,7 +1396,7 @@ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "log2src" version = "0.1.0" -source = "git+https://github.com/tstack/log2src.git?branch=claude/project-thread-dj3xkq#98c865eb4cb8c3e6eb51d6a119087f5547119b13" +source = "git+https://github.com/tstack/log2src.git?branch=caching1#6c6ebccd15d76eb2eb5c641b2848a3f333940283" dependencies = [ "aho-corasick", "bincode", @@ -1905,7 +1905,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2222,7 +2222,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2706,7 +2706,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] diff --git a/src/third-party/lnav-rs-ext/Cargo.toml b/src/third-party/lnav-rs-ext/Cargo.toml index f04e676c141..4070ddca0ff 100644 --- a/src/third-party/lnav-rs-ext/Cargo.toml +++ b/src/third-party/lnav-rs-ext/Cargo.toml @@ -27,7 +27,7 @@ prqlc = "0.13.10" cxx = "1.0" log = "0.4" miette = "7.6.0" -log2src = {git = "https://github.com/tstack/log2src.git", branch = "claude/project-thread-dj3xkq"} +log2src = {git = "https://github.com/tstack/log2src.git", branch = "caching1"} # log2src = {path = "/Users/tstack/github/log2src"} rouille = "3.6.2" cookie = "0.18.1" From 7ac98d3a627e6f31aba42d2ca1e09acdbb863141 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 01:40:39 +0000 Subject: [PATCH 3/5] [log2src] give statement levels as an enum instead of a string Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KTECZJ1AReK76iN7tgD1KY --- src/all_logs_vtab.cc | 52 +++++++++++++- src/all_logs_vtab.hh | 10 +++ src/log_data_helper.cc | 5 +- src/log_stmt_vtab.cc | 8 ++- .../lnav-rs-ext/lnav_rs_ext.cxx.cc | 30 ++++++-- .../lnav-rs-ext/lnav_rs_ext.cxx.hh | 26 +++++-- src/third-party/lnav-rs-ext/src/ext_access.rs | 11 ++- src/third-party/lnav-rs-ext/src/lib.rs | 72 +++++++++++-------- 8 files changed, 162 insertions(+), 52 deletions(-) diff --git a/src/all_logs_vtab.cc b/src/all_logs_vtab.cc index 98dd506084d..11f67dc8453 100644 --- a/src/all_logs_vtab.cc +++ b/src/all_logs_vtab.cc @@ -467,14 +467,62 @@ all_logs_vtab::find_statement(const shared_buffer_ref& sbr, auto find_options = lnav_rs_ext::FindLogOptions{ .extract_variables = extract_variables, }; - const auto& level_name = level_names[level]; return lnav_rs_ext::find_log_statement_json( file_rust_str, lineno, - rust::Str(level_name.data(), level_name.length()), + to_stmt_level(level), rust::Str(body_sf.data(), body_sf.length()), find_options); } + +lnav_rs_ext::StmtLevel +all_logs_vtab::to_stmt_level(log_level_t level) +{ + switch (level) { + case LEVEL_TRACE: + return lnav_rs_ext::StmtLevel::trace; + case LEVEL_DEBUG5: + case LEVEL_DEBUG4: + case LEVEL_DEBUG3: + case LEVEL_DEBUG2: + case LEVEL_DEBUG: + return lnav_rs_ext::StmtLevel::debug; + case LEVEL_INFO: + case LEVEL_STATS: + case LEVEL_NOTICE: + return lnav_rs_ext::StmtLevel::info; + case LEVEL_WARNING: + return lnav_rs_ext::StmtLevel::warning; + case LEVEL_ERROR: + return lnav_rs_ext::StmtLevel::error; + case LEVEL_CRITICAL: + case LEVEL_FATAL: + return lnav_rs_ext::StmtLevel::fatal; + default: + return lnav_rs_ext::StmtLevel::unknown; + } +} + +log_level_t +all_logs_vtab::from_stmt_level(lnav_rs_ext::StmtLevel level) +{ + switch (level) { + case lnav_rs_ext::StmtLevel::trace: + return LEVEL_TRACE; + case lnav_rs_ext::StmtLevel::debug: + return LEVEL_DEBUG; + case lnav_rs_ext::StmtLevel::info: + return LEVEL_INFO; + case lnav_rs_ext::StmtLevel::warning: + return LEVEL_WARNING; + case lnav_rs_ext::StmtLevel::error: + return LEVEL_ERROR; + case lnav_rs_ext::StmtLevel::fatal: + return LEVEL_FATAL; + default: + return LEVEL_UNKNOWN; + } +} #endif bool diff --git a/src/all_logs_vtab.hh b/src/all_logs_vtab.hh index 620cd3e5be9..3fc69c060f1 100644 --- a/src/all_logs_vtab.hh +++ b/src/all_logs_vtab.hh @@ -43,6 +43,7 @@ #ifdef HAVE_RUST_DEPS namespace lnav_rs_ext { struct FindLogResultJson; +enum class StmtLevel : std::uint8_t; } #endif @@ -148,6 +149,15 @@ public: const logline_value_vector& values, log_level_t level, bool extract_variables); + + /** + * The level of a message as log2src tells them apart. The levels it + * doesn't have are folded into the nearest one it does. + */ + static lnav_rs_ext::StmtLevel to_stmt_level(log_level_t level); + + /** The level of a log statement found by log2src. */ + static log_level_t from_stmt_level(lnav_rs_ext::StmtLevel level); #endif private: diff --git a/src/log_data_helper.cc b/src/log_data_helper.cc index 771a24b386d..c4ac53dc837 100644 --- a/src/log_data_helper.cc +++ b/src/log_data_helper.cc @@ -33,6 +33,8 @@ #include "log_data_helper.hh" +#include "all_logs_vtab.hh" + #include "base/ansi_scrubber.hh" #include "base/attr_line.builder.hh" #include "config.h" @@ -268,11 +270,10 @@ log_data_helper::parse_body() } this->ldh_src_ref = std::nullopt; this->ldh_src_vars.clear(); - const auto& level_name = level_names[this->ldh_line->get_msg_level()]; auto find_res = lnav_rs_ext::find_log_statement( file_rust_str, lineno, - rust::Str(level_name.data(), level_name.length()), + all_logs_vtab::to_stmt_level(this->ldh_line->get_msg_level()), body_rust_str); if (find_res != nullptr) { this->ldh_src_ref = lnav::src_ref{ diff --git a/src/log_stmt_vtab.cc b/src/log_stmt_vtab.cc index f1f7d1c00e4..09ff719c78e 100644 --- a/src/log_stmt_vtab.cc +++ b/src/log_stmt_vtab.cc @@ -135,10 +135,14 @@ CREATE TABLE lnav_db.source_log_stmt ( to_sqlite(ctx, (int64_t) stmt.src.block_id); break; case log_stmt_col::level: - if (stmt.level.empty()) { + if (stmt.level == lnav_rs_ext::StmtLevel::unknown) { sqlite3_result_null(ctx); } else { - to_sqlite(ctx, (std::string) stmt.level); + const auto& name + = level_names[all_logs_vtab::from_stmt_level( + stmt.level)]; + sqlite3_result_text( + ctx, name.data(), name.length(), SQLITE_STATIC); } break; case log_stmt_col::pattern: diff --git a/src/third-party/lnav-rs-ext/lnav_rs_ext.cxx.cc b/src/third-party/lnav-rs-ext/lnav_rs_ext.cxx.cc index 1b55653efb3..9f5f603f642 100644 --- a/src/third-party/lnav-rs-ext/lnav_rs_ext.cxx.cc +++ b/src/third-party/lnav-rs-ext/lnav_rs_ext.cxx.cc @@ -853,6 +853,7 @@ namespace lnav_rs_ext { struct PollResult; struct ExecError; struct ExecResult; + enum class StmtLevel : ::std::uint8_t; enum class LnavLogLevel : ::std::uint8_t; struct StartExtResult; } @@ -999,9 +1000,9 @@ struct FindLogResult final { // `src` as `find_log_statement_json()` gives it, which the schema hashes. ::rust::String src_json; ::rust::String pattern; - // The statement's level, as lnav names it, or empty if log2src - // could not tell, like for a `printf()`. - ::rust::String level; + // The statement's level, or `unknown` if log2src could not tell, + // like for a `printf()`. + ::lnav_rs_ext::StmtLevel level; ::rust::Vec<::lnav_rs_ext::VarPair> variables; using IsRelocatable = ::std::true_type; @@ -1121,6 +1122,21 @@ struct ExecResult final { }; #endif // CXXBRIDGE1_STRUCT_lnav_rs_ext$ExecResult +#ifndef CXXBRIDGE1_ENUM_lnav_rs_ext$StmtLevel +#define CXXBRIDGE1_ENUM_lnav_rs_ext$StmtLevel +// The level of a log statement or message, as far as log2src can +// tell them apart. +enum class StmtLevel : ::std::uint8_t { + unknown = 0, + trace = 1, + debug = 2, + info = 3, + warning = 4, + error = 5, + fatal = 6, +}; +#endif // CXXBRIDGE1_ENUM_lnav_rs_ext$StmtLevel + #ifndef CXXBRIDGE1_ENUM_lnav_rs_ext$LnavLogLevel #define CXXBRIDGE1_ENUM_lnav_rs_ext$LnavLogLevel enum class LnavLogLevel : ::std::uint8_t { @@ -1193,9 +1209,9 @@ void lnav_rs_ext$cxxbridge1$205$discover_srcs() noexcept; void lnav_rs_ext$cxxbridge1$205$get_status(::lnav_rs_ext::ExtProgress *return$) noexcept; -::lnav_rs_ext::FindLogResult *lnav_rs_ext$cxxbridge1$205$find_log_statement(::rust::Str file, ::std::size_t line, ::rust::Str level, ::rust::Str body) noexcept; +::lnav_rs_ext::FindLogResult *lnav_rs_ext$cxxbridge1$205$find_log_statement(::rust::Str file, ::std::size_t line, ::lnav_rs_ext::StmtLevel level, ::rust::Str body) noexcept; -::lnav_rs_ext::FindLogResultJson *lnav_rs_ext$cxxbridge1$205$find_log_statement_json(::rust::Str file, ::std::size_t line, ::rust::Str level, ::rust::Str body, ::lnav_rs_ext::FindLogOptions const &options) noexcept; +::lnav_rs_ext::FindLogResultJson *lnav_rs_ext$cxxbridge1$205$find_log_statement_json(::rust::Str file, ::std::size_t line, ::lnav_rs_ext::StmtLevel level, ::rust::Str body, ::lnav_rs_ext::FindLogOptions const &options) noexcept; void lnav_rs_ext$cxxbridge1$205$get_log_statements_for(::rust::Str file, ::rust::Vec<::lnav_rs_ext::FindLogResult> *return$) noexcept; @@ -1232,11 +1248,11 @@ ::lnav_rs_ext::ExtProgress get_status() noexcept { return ::std::move(return$.value); } -::std::unique_ptr<::lnav_rs_ext::FindLogResult> find_log_statement(::rust::Str file, ::std::size_t line, ::rust::Str level, ::rust::Str body) noexcept { +::std::unique_ptr<::lnav_rs_ext::FindLogResult> find_log_statement(::rust::Str file, ::std::size_t line, ::lnav_rs_ext::StmtLevel level, ::rust::Str body) noexcept { return ::std::unique_ptr<::lnav_rs_ext::FindLogResult>(lnav_rs_ext$cxxbridge1$205$find_log_statement(file, line, level, body)); } -::std::unique_ptr<::lnav_rs_ext::FindLogResultJson> find_log_statement_json(::rust::Str file, ::std::size_t line, ::rust::Str level, ::rust::Str body, ::lnav_rs_ext::FindLogOptions const &options) noexcept { +::std::unique_ptr<::lnav_rs_ext::FindLogResultJson> find_log_statement_json(::rust::Str file, ::std::size_t line, ::lnav_rs_ext::StmtLevel level, ::rust::Str body, ::lnav_rs_ext::FindLogOptions const &options) noexcept { return ::std::unique_ptr<::lnav_rs_ext::FindLogResultJson>(lnav_rs_ext$cxxbridge1$205$find_log_statement_json(file, line, level, body, options)); } diff --git a/src/third-party/lnav-rs-ext/lnav_rs_ext.cxx.hh b/src/third-party/lnav-rs-ext/lnav_rs_ext.cxx.hh index 8af7bfea618..58acfc2a8af 100644 --- a/src/third-party/lnav-rs-ext/lnav_rs_ext.cxx.hh +++ b/src/third-party/lnav-rs-ext/lnav_rs_ext.cxx.hh @@ -817,6 +817,7 @@ namespace lnav_rs_ext { struct PollResult; struct ExecError; struct ExecResult; + enum class StmtLevel : ::std::uint8_t; enum class LnavLogLevel : ::std::uint8_t; struct StartExtResult; } @@ -963,9 +964,9 @@ struct FindLogResult final { // `src` as `find_log_statement_json()` gives it, which the schema hashes. ::rust::String src_json; ::rust::String pattern; - // The statement's level, as lnav names it, or empty if log2src - // could not tell, like for a `printf()`. - ::rust::String level; + // The statement's level, or `unknown` if log2src could not tell, + // like for a `printf()`. + ::lnav_rs_ext::StmtLevel level; ::rust::Vec<::lnav_rs_ext::VarPair> variables; using IsRelocatable = ::std::true_type; @@ -1085,6 +1086,21 @@ struct ExecResult final { }; #endif // CXXBRIDGE1_STRUCT_lnav_rs_ext$ExecResult +#ifndef CXXBRIDGE1_ENUM_lnav_rs_ext$StmtLevel +#define CXXBRIDGE1_ENUM_lnav_rs_ext$StmtLevel +// The level of a log statement or message, as far as log2src can +// tell them apart. +enum class StmtLevel : ::std::uint8_t { + unknown = 0, + trace = 1, + debug = 2, + info = 3, + warning = 4, + error = 5, + fatal = 6, +}; +#endif // CXXBRIDGE1_ENUM_lnav_rs_ext$StmtLevel + #ifndef CXXBRIDGE1_ENUM_lnav_rs_ext$LnavLogLevel #define CXXBRIDGE1_ENUM_lnav_rs_ext$LnavLogLevel enum class LnavLogLevel : ::std::uint8_t { @@ -1116,9 +1132,9 @@ void discover_srcs() noexcept; ::lnav_rs_ext::ExtProgress get_status() noexcept; -::std::unique_ptr<::lnav_rs_ext::FindLogResult> find_log_statement(::rust::Str file, ::std::size_t line, ::rust::Str level, ::rust::Str body) noexcept; +::std::unique_ptr<::lnav_rs_ext::FindLogResult> find_log_statement(::rust::Str file, ::std::size_t line, ::lnav_rs_ext::StmtLevel level, ::rust::Str body) noexcept; -::std::unique_ptr<::lnav_rs_ext::FindLogResultJson> find_log_statement_json(::rust::Str file, ::std::size_t line, ::rust::Str level, ::rust::Str body, ::lnav_rs_ext::FindLogOptions const &options) noexcept; +::std::unique_ptr<::lnav_rs_ext::FindLogResultJson> find_log_statement_json(::rust::Str file, ::std::size_t line, ::lnav_rs_ext::StmtLevel level, ::rust::Str body, ::lnav_rs_ext::FindLogOptions const &options) noexcept; ::rust::Vec<::lnav_rs_ext::FindLogResult> get_log_statements_for(::rust::Str file) noexcept; diff --git a/src/third-party/lnav-rs-ext/src/ext_access.rs b/src/third-party/lnav-rs-ext/src/ext_access.rs index 735037a3570..3d577cf952c 100644 --- a/src/third-party/lnav-rs-ext/src/ext_access.rs +++ b/src/third-party/lnav-rs-ext/src/ext_access.rs @@ -41,7 +41,9 @@ fn do_login(request: &Request) -> Response { let session_cookie = Cookie::build(("lnav_session_id", session_id)) .path("/") .build(); - let target = request.get_param("target").unwrap_or_else(|| "/".to_string()); + let target = request + .get_param("target") + .unwrap_or_else(|| "/".to_string()); return Response::redirect_302(target) .with_additional_header("Set-Cookie", session_cookie.to_string()); } else { @@ -193,8 +195,7 @@ fn do_exec(request: &Request) -> Response { } fn do_poll(request: &Request) -> Response { - let mut body = - try_or_400!(input::json_input::>(request)).unwrap_or_default(); + let mut body = try_or_400!(input::json_input::>(request)).unwrap_or_default(); // A request has no lasting connection, so lnav tells pollers apart by // what the request carries: the browser session, the editor's client ID, // or, failing those, the user agent. @@ -314,9 +315,7 @@ pub fn stop_server() { // after the server is dropped, so wait for the port to be released // to let the caller bind to it again. let deadline = Instant::now() + Duration::from_secs(1); - while TcpListener::bind((Ipv4Addr::LOCALHOST, port)).is_err() - && Instant::now() < deadline - { + while TcpListener::bind((Ipv4Addr::LOCALHOST, port)).is_err() && Instant::now() < deadline { thread::sleep(Duration::from_millis(10)); } } diff --git a/src/third-party/lnav-rs-ext/src/lib.rs b/src/third-party/lnav-rs-ext/src/lib.rs index c650c0798c8..313cd0b3caf 100644 --- a/src/third-party/lnav-rs-ext/src/lib.rs +++ b/src/third-party/lnav-rs-ext/src/lib.rs @@ -6,7 +6,8 @@ mod ext_access; use crate::ext_access::{set_login_otp, start_server, stop_server}; use crate::ffi::{ get_lnav_log_level, notify_completion, notify_pollers, ExtError, ExtProgress, FindLogOptions, - FindLogResult, FindLogResultJson, LnavLogLevel, SourceDetails, StartExtResult, Status, VarPair, + FindLogResult, FindLogResultJson, LnavLogLevel, SourceDetails, StartExtResult, Status, + StmtLevel, VarPair, }; use cxx::UniquePtr; use log::{Level, Log, Metadata, Record}; @@ -234,9 +235,9 @@ mod ffi { /// `src` as `find_log_statement_json()` gives it, which the schema hashes. pub src_json: String, pub pattern: String, - /// The statement's level, as lnav names it, or empty if log2src - /// could not tell, like for a `printf()`. - pub level: String, + /// The statement's level, or `unknown` if log2src could not tell, + /// like for a `printf()`. + pub level: StmtLevel, pub variables: Vec, } @@ -323,6 +324,19 @@ mod ffi { pub error: ExecError, } + /// The level of a log statement or message, as far as log2src can + /// tell them apart. + #[derive(Debug)] + enum StmtLevel { + unknown, + trace, + debug, + info, + warning, + error, + fatal, + } + enum LnavLogLevel { trace, debug, @@ -375,13 +389,13 @@ mod ffi { fn find_log_statement( file: &str, line: usize, - level: &str, + level: StmtLevel, body: &str, ) -> UniquePtr; fn find_log_statement_json( file: &str, line: usize, - level: &str, + level: StmtLevel, body: &str, options: &FindLogOptions, ) -> UniquePtr; @@ -593,7 +607,7 @@ impl From<&SourceRef> for FindLogResult { }, src_json: src_json(value), pattern: value.pattern_str.to_string(), - level: lnav_level_name(value.level).to_string(), + level: value.level.into(), variables: vec![], } } @@ -609,35 +623,38 @@ impl FindLogResult { } } -/// The name lnav uses for a statement's level. -fn lnav_level_name(level: LogLevel) -> &'static str { - match level { - LogLevel::Trace => "trace", - LogLevel::Debug => "debug", - LogLevel::Info => "info", - LogLevel::Warn => "warning", - LogLevel::Error => "error", - LogLevel::Fatal => "fatal", - LogLevel::Unknown => "", +impl From for StmtLevel { + fn from(value: LogLevel) -> Self { + match value { + LogLevel::Trace => StmtLevel::trace, + LogLevel::Debug => StmtLevel::debug, + LogLevel::Info => StmtLevel::info, + LogLevel::Warn => StmtLevel::warning, + LogLevel::Error => StmtLevel::error, + LogLevel::Fatal => StmtLevel::fatal, + LogLevel::Unknown => StmtLevel::unknown, + } } } -/// The level of a message, as lnav names it, in the form log2src parses, so -/// that statements with the same level are preferred. The levels that -/// log2src doesn't have are folded into the nearest one it does. -fn log2src_level_text(level: &str) -> Option<&str> { +/// The level of a message in the form log2src parses, so that statements +/// with the same level are preferred. +fn log2src_level_text(level: StmtLevel) -> Option<&'static str> { match level { - "" | "unknown" | "invalid" | "header" => None, - "stats" => Some("info"), - _ if level.starts_with("debug") => Some("debug"), - _ => Some(level), + StmtLevel::trace => Some("trace"), + StmtLevel::debug => Some("debug"), + StmtLevel::info => Some("info"), + StmtLevel::warning => Some("warn"), + StmtLevel::error => Some("error"), + StmtLevel::fatal => Some("fatal"), + _ => None, } } fn find_log_statement( file: &str, lineno: usize, - level: &str, + level: StmtLevel, body: &str, ) -> UniquePtr { let log_matcher = LOG_MATCHER.read().unwrap(); @@ -691,7 +708,7 @@ fn src_json(src_ref: &SourceRef) -> String { fn find_log_statement_json( file: &str, lineno: usize, - level: &str, + level: StmtLevel, body: &str, options: &FindLogOptions, ) -> UniquePtr { @@ -703,7 +720,6 @@ fn find_log_statement_json( .with_body(Some(body)) .build(body); - if let Some(LogMapping { variables, src_ref: Some(src_ref), From 631e8dcf25859b8ca1176cce2aa39c85a1bd46b6 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 01:40:48 +0000 Subject: [PATCH 4/5] [log2src] undo the formatting of ext_access.rs Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KTECZJ1AReK76iN7tgD1KY --- src/third-party/lnav-rs-ext/src/ext_access.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/third-party/lnav-rs-ext/src/ext_access.rs b/src/third-party/lnav-rs-ext/src/ext_access.rs index 3d577cf952c..735037a3570 100644 --- a/src/third-party/lnav-rs-ext/src/ext_access.rs +++ b/src/third-party/lnav-rs-ext/src/ext_access.rs @@ -41,9 +41,7 @@ fn do_login(request: &Request) -> Response { let session_cookie = Cookie::build(("lnav_session_id", session_id)) .path("/") .build(); - let target = request - .get_param("target") - .unwrap_or_else(|| "/".to_string()); + let target = request.get_param("target").unwrap_or_else(|| "/".to_string()); return Response::redirect_302(target) .with_additional_header("Set-Cookie", session_cookie.to_string()); } else { @@ -195,7 +193,8 @@ fn do_exec(request: &Request) -> Response { } fn do_poll(request: &Request) -> Response { - let mut body = try_or_400!(input::json_input::>(request)).unwrap_or_default(); + let mut body = + try_or_400!(input::json_input::>(request)).unwrap_or_default(); // A request has no lasting connection, so lnav tells pollers apart by // what the request carries: the browser session, the editor's client ID, // or, failing those, the user agent. @@ -315,7 +314,9 @@ pub fn stop_server() { // after the server is dropped, so wait for the port to be released // to let the caller bind to it again. let deadline = Instant::now() + Duration::from_secs(1); - while TcpListener::bind((Ipv4Addr::LOCALHOST, port)).is_err() && Instant::now() < deadline { + while TcpListener::bind((Ipv4Addr::LOCALHOST, port)).is_err() + && Instant::now() < deadline + { thread::sleep(Duration::from_millis(10)); } } From 108bb324934200270f75dfceaaab516bcbe0275d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 02:31:04 +0000 Subject: [PATCH 5/5] [log2src] pass the message level with with_log_level() Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KTECZJ1AReK76iN7tgD1KY --- src/third-party/lnav-rs-ext/Cargo.lock | 12 ++++++------ src/third-party/lnav-rs-ext/src/lib.rs | 26 +++++++++++++------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/third-party/lnav-rs-ext/Cargo.lock b/src/third-party/lnav-rs-ext/Cargo.lock index 588eeeaa35d..a255d77c5ec 100644 --- a/src/third-party/lnav-rs-ext/Cargo.lock +++ b/src/third-party/lnav-rs-ext/Cargo.lock @@ -799,7 +799,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1258,7 +1258,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1396,7 +1396,7 @@ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "log2src" version = "0.1.0" -source = "git+https://github.com/tstack/log2src.git?branch=caching1#6c6ebccd15d76eb2eb5c641b2848a3f333940283" +source = "git+https://github.com/tstack/log2src.git?branch=caching1#d841076479ddac452b529787f5f2ab6463c5635e" dependencies = [ "aho-corasick", "bincode", @@ -1905,7 +1905,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2222,7 +2222,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2706,7 +2706,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/src/third-party/lnav-rs-ext/src/lib.rs b/src/third-party/lnav-rs-ext/src/lib.rs index 313cd0b3caf..2b595c7aa4b 100644 --- a/src/third-party/lnav-rs-ext/src/lib.rs +++ b/src/third-party/lnav-rs-ext/src/lib.rs @@ -637,17 +637,17 @@ impl From for StmtLevel { } } -/// The level of a message in the form log2src parses, so that statements -/// with the same level are preferred. -fn log2src_level_text(level: StmtLevel) -> Option<&'static str> { - match level { - StmtLevel::trace => Some("trace"), - StmtLevel::debug => Some("debug"), - StmtLevel::info => Some("info"), - StmtLevel::warning => Some("warn"), - StmtLevel::error => Some("error"), - StmtLevel::fatal => Some("fatal"), - _ => None, +impl From for LogLevel { + fn from(value: StmtLevel) -> Self { + match value { + StmtLevel::trace => LogLevel::Trace, + StmtLevel::debug => LogLevel::Debug, + StmtLevel::info => LogLevel::Info, + StmtLevel::warning => LogLevel::Warn, + StmtLevel::error => LogLevel::Error, + StmtLevel::fatal => LogLevel::Fatal, + _ => LogLevel::Unknown, + } } } @@ -661,7 +661,7 @@ fn find_log_statement( let log_ref = LogRefBuilder::new() .with_file(if file.is_empty() { None } else { Some(file) }) .with_lineno(if file.is_empty() { None } else { Some(lineno) }) - .with_level(log2src_level_text(level)) + .with_log_level(Some(level.into())) .with_body(Some(body)) .build(body); @@ -716,7 +716,7 @@ fn find_log_statement_json( let log_ref = LogRefBuilder::new() .with_file(if file.is_empty() { None } else { Some(file) }) .with_lineno(if file.is_empty() { None } else { Some(lineno) }) - .with_level(log2src_level_text(level)) + .with_log_level(Some(level.into())) .with_body(Some(body)) .build(body);