app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 10eb301637ea6dc276476139defb7304dad9a660
parent 6d6820114684944ad0fa336ec15cbec534aa2560
Author: triesap <triesap@radroots.dev>
Date:   Tue, 20 Jan 2026 16:51:40 +0000

app: limit init log persistence to errors

- add filtered buffer flush helper for warn and error

- use critical flush for init and health runs

- keep full flush for logs page refresh

- add unit tests for filtered flush behavior

Diffstat:
Mapp/src/app.rs | 8++++++--
Mapp/src/health.rs | 4++--
Mapp/src/lib.rs | 1+
Mapp/src/logging.rs | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 71 insertions(+), 4 deletions(-)

diff --git a/app/src/app.rs b/app/src/app.rs @@ -15,7 +15,7 @@ use crate::{ app_init_stage_set, app_init_total_add, app_init_total_unknown, - app_log_buffer_flush, + app_log_buffer_flush_critical, app_log_debug_emit, app_log_error_emit, app_log_error_store, @@ -162,7 +162,11 @@ fn HomePage() -> impl IntoView { } match app_init_backends(config).await { Ok(value) => { - let _ = app_log_buffer_flush(&value.datastore, &value.config.datastore.key_maps).await; + let _ = app_log_buffer_flush_critical( + &value.datastore, + &value.config.datastore.key_maps, + ) + .await; backends.set(Some(value)); app_init_mark_completed(); let stage = AppInitStage::Ready; diff --git a/app/src/health.rs b/app/src/health.rs @@ -84,7 +84,7 @@ use crate::{ app_datastore_has_config, app_datastore_key_nostr_key, app_datastore_read_app_data, - app_log_buffer_flush, + app_log_buffer_flush_critical, app_log_debug_emit, app_log_entry_new, app_log_entry_record, @@ -300,7 +300,7 @@ pub async fn app_health_check_all_logged<T: RadrootsClientDatastore, K: Radroots key_maps: &AppKeyMapConfig, ) -> AppHealthReport { let report = app_health_check_all(datastore, keystore, notifications, tangle, key_maps).await; - let _ = app_log_buffer_flush(datastore, key_maps).await; + let _ = app_log_buffer_flush_critical(datastore, key_maps).await; report } diff --git a/app/src/lib.rs b/app/src/lib.rs @@ -55,6 +55,7 @@ pub use logging::{ app_log_entry_record, app_log_entry_store, app_log_buffer_drain, + app_log_buffer_flush_critical, app_log_buffer_flush, app_log_buffer_push, app_log_entries_dump, diff --git a/app/src/logging.rs b/app/src/logging.rs @@ -363,6 +363,10 @@ pub fn app_log_buffer_drain() -> Vec<AppLogEntry> { } } +fn app_log_entry_should_persist(level: AppLogLevel) -> bool { + matches!(level, AppLogLevel::Warn | AppLogLevel::Error) +} + pub async fn app_log_buffer_flush<T: RadrootsClientDatastore>( datastore: &T, key_maps: &AppKeyMapConfig, @@ -384,6 +388,42 @@ pub async fn app_log_buffer_flush<T: RadrootsClientDatastore>( Ok(stored) } +pub async fn app_log_buffer_flush_critical<T: RadrootsClientDatastore>( + datastore: &T, + key_maps: &AppKeyMapConfig, +) -> AppLogResult<usize> { + let entries = app_log_buffer_drain(); + let mut keep = Vec::new(); + let mut persist = Vec::new(); + for entry in entries { + if app_log_entry_should_persist(entry.level) { + persist.push(entry); + } else { + keep.push(entry); + } + } + let mut stored = 0; + let mut iter = persist.into_iter(); + while let Some(entry) = iter.next() { + if let Err(err) = app_log_entry_store(datastore, key_maps, &entry).await { + app_log_buffer_push(entry); + for remaining in iter { + app_log_buffer_push(remaining); + } + for remaining in keep { + app_log_buffer_push(remaining); + } + return Err(err); + } + stored += 1; + } + for entry in keep { + app_log_buffer_push(entry); + } + let _ = app_log_entries_prune(datastore, key_maps, APP_LOG_MAX_ENTRIES).await?; + Ok(stored) +} + pub fn app_log_entry_prefix(key_maps: &AppKeyMapConfig) -> AppLogResult<String> { let param = app_datastore_param_key(key_maps, "log_entry")?; Ok(param("")) @@ -518,6 +558,7 @@ mod tests { app_log_entry_key, app_log_entry_prefix, app_log_buffer_drain, + app_log_buffer_flush_critical, app_log_buffer_flush, app_log_buffer_push, app_log_metadata, @@ -754,6 +795,27 @@ mod tests { } #[test] + fn log_buffer_flush_critical_keeps_debug_entries() { + let _guard = LOG_TEST_LOCK.lock().unwrap_or_else(|err| err.into_inner()); + let _ = app_log_buffer_drain(); + let debug = app_log_entry_new(AppLogLevel::Debug, "log.code.debug", "debug", None); + let error = app_log_entry_new(AppLogLevel::Error, "log.code.error", "error", None); + app_log_buffer_push(debug.clone()); + app_log_buffer_push(error.clone()); + let datastore = TestDatastore::new(Vec::new()); + let key_maps = app_key_maps_default(); + let stored = futures::executor::block_on(app_log_buffer_flush_critical( + &datastore, + &key_maps, + )) + .expect("flush"); + assert_eq!(stored, 1); + let remaining = app_log_buffer_drain(); + assert_eq!(remaining.len(), 1); + assert_eq!(remaining[0].id, debug.id); + } + + #[test] fn log_entry_prefix_uses_log_key() { let key_maps = app_key_maps_default(); let prefix = app_log_entry_prefix(&key_maps).expect("prefix");