feat(perf): live runtime logs tab in Performance Probe (#946)

* feat(perf): live runtime logs tab in Performance Probe

Add a Logs tab that streams the backend runtime log ring buffer in-app, so the
stdout/stderr console (unreachable on Windows without exporting) can be read
live. The buffer now tracks a monotonic seq; a new tail_runtime_logs command
returns lines incrementally and get_logging_mode reports the current depth.

The tab has a depth switch (off/normal/debug) mirroring app settings, a line cap
(500-5000), pause/clear, auto-follow, and an ordered comma-separated word filter
where a plain word includes and a -word excludes, applied left to right as
layers (sequence matters).

* docs(changelog): note Performance Probe logs tab (PR #946)

Add CHANGELOG entry and credits line for the live runtime logs tab.

* fix(perf): pin log view position when scrolled up

Auto-scroll keeps the logs tab at the tail, but once the user scrolls up the
view now stays put — the previously-topmost line is re-pinned each tick while
the log keeps appending below for further scrolling. History under the viewport
is no longer trimmed while scrolled up (kept up to the ring-buffer ceiling); the
cap is re-applied when following resumes. Buffer overflow is shown in the status
line instead of an injected marker row.

* fix(perf): scope logs tab to its own internal scroll

The whole probe body scrolled (controls + filter + log) because the log
container sized via height:100%, which WebKitGTK does not resolve against the
flex body. Make the body a flex column with hidden overflow on the Logs tab and
let the log view flex-fill, so depth/keep/pause/clear and the filter stay fixed
while only the log lines scroll.
This commit is contained in:
cucadmuh
2026-06-02 11:40:36 +03:00
committed by GitHub
parent c6df05e576
commit 975bb6d9af
12 changed files with 666 additions and 17 deletions
@@ -24,11 +24,48 @@ pub(crate) fn set_logging_mode(mode: String) -> Result<(), String> {
crate::logging::set_logging_mode_from_str(&mode)
}
#[tauri::command]
pub(crate) fn get_logging_mode() -> String {
crate::logging::current_mode_str().to_string()
}
#[tauri::command]
pub(crate) fn export_runtime_logs(path: String) -> Result<usize, String> {
crate::logging::export_logs_to_file(&path)
}
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct LogLineDto {
pub seq: u64,
pub text: String,
}
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct LogTailDto {
pub lines: Vec<LogLineDto>,
pub last_seq: u64,
pub dropped: bool,
}
/// Incremental tail of the in-memory runtime log buffer for the Performance
/// Probe Logs tab. `after_seq` is the highest seq the UI already has (omit for
/// the initial fetch of the most recent `max` lines).
#[tauri::command]
pub(crate) fn tail_runtime_logs(after_seq: Option<u64>, max: Option<usize>) -> LogTailDto {
let tail = crate::logging::tail_logs(after_seq, max.unwrap_or(2000));
LogTailDto {
lines: tail
.lines
.into_iter()
.map(|l| LogLineDto { seq: l.seq, text: l.text })
.collect(),
last_seq: tail.last_seq,
dropped: tail.dropped,
}
}
#[tauri::command]
pub(crate) fn frontend_debug_log(scope: String, message: String) -> Result<(), String> {
crate::app_deprintln!("[frontend][{}] {}", scope, message);
+2 -2
View File
@@ -16,8 +16,8 @@ pub(crate) use cli_bridge::{
cli_publish_server_list,
};
pub(crate) use core::{
exit_app, export_runtime_logs, frontend_debug_log, greet, set_logging_mode,
set_subsonic_wire_user_agent,
exit_app, export_runtime_logs, frontend_debug_log, get_logging_mode, greet, set_logging_mode,
set_subsonic_wire_user_agent, tail_runtime_logs,
};
pub(crate) use perf::performance_cpu_snapshot;
pub(crate) use platform::{