plugin logging

This commit is contained in:
Bradley Nelson
2026-06-16 23:00:23 -06:00
parent 803150635c
commit 4427b1613b
17 changed files with 1928 additions and 12 deletions
+49
View File
@@ -961,6 +961,25 @@ pub struct PluginConfig {
/// Hard cap on the serialized event payload handed to a plugin. Default:
/// 256 KiB. Env: `OXICLOUD_PLUGIN_MAX_INPUT_BYTES`.
pub max_input_bytes: usize,
/// Directory under which per-plugin log files live (one subdir per plugin id,
/// holding `events.jsonl` + rotated `events.jsonl.<ts>.gz` + `retention.json`).
/// Default: `{storage_path}/.plugin-logs`. Env: `OXICLOUD_PLUGIN_LOG_DIR`.
pub log_dir: Option<PathBuf>,
/// Size at which a plugin's active `events.jsonl` is rotated into a new gzip
/// segment. Default: 5 MiB. Env: `OXICLOUD_PLUGIN_LOG_MAX_FILE_BYTES`.
pub log_max_file_bytes: u64,
/// Coarse ceiling on the number of rotated `.gz` segments kept per plugin
/// (file-rotate `FileLimit::MaxFiles`); the real limits are the per-plugin
/// retention sweep. Default: 10. Env: `OXICLOUD_PLUGIN_LOG_MAX_SEGMENTS`.
pub log_max_segments: u32,
/// Default age (in days) past which a plugin's rotated log segments are
/// pruned by the maintenance sweep. Overridable per plugin via its
/// `retention.json`. Default: 30. Env: `OXICLOUD_PLUGIN_LOG_RETENTION_DAYS`.
pub log_retention_days: u32,
/// Default aggregate byte cap on kept log segments for a single plugin; the
/// sweep deletes oldest-first past this. Overridable per plugin. Default:
/// 256 MiB. Env: `OXICLOUD_PLUGIN_LOG_TOTAL_MAX_BYTES`.
pub log_total_max_bytes: u64,
}
impl Default for PluginConfig {
@@ -971,6 +990,11 @@ impl Default for PluginConfig {
invocation_timeout_ms: 250,
max_memory_pages: 256,
max_input_bytes: 256 * 1024,
log_dir: None,
log_max_file_bytes: 5 * 1024 * 1024,
log_max_segments: 10,
log_retention_days: 30,
log_total_max_bytes: 256 * 1024 * 1024,
}
}
}
@@ -1386,6 +1410,31 @@ impl AppConfig {
{
config.plugins.max_input_bytes = val;
}
if let Ok(dir) = env::var("OXICLOUD_PLUGIN_LOG_DIR")
&& !dir.trim().is_empty()
{
config.plugins.log_dir = Some(PathBuf::from(dir.trim()));
}
if let Ok(v) = env::var("OXICLOUD_PLUGIN_LOG_MAX_FILE_BYTES").map(|v| v.parse::<u64>())
&& let Ok(val) = v
{
config.plugins.log_max_file_bytes = val;
}
if let Ok(v) = env::var("OXICLOUD_PLUGIN_LOG_MAX_SEGMENTS").map(|v| v.parse::<u32>())
&& let Ok(val) = v
{
config.plugins.log_max_segments = val;
}
if let Ok(v) = env::var("OXICLOUD_PLUGIN_LOG_RETENTION_DAYS").map(|v| v.parse::<u32>())
&& let Ok(val) = v
{
config.plugins.log_retention_days = val;
}
if let Ok(v) = env::var("OXICLOUD_PLUGIN_LOG_TOTAL_MAX_BYTES").map(|v| v.parse::<u64>())
&& let Ok(val) = v
{
config.plugins.log_total_max_bytes = val;
}
if let Ok(v) = env::var("OXICLOUD_EXPOSE_SYSTEM_USERS").map(|v| v.parse::<bool>())
&& let Ok(val) = v
+23 -2
View File
@@ -613,9 +613,18 @@ impl AppServiceFactory {
.plugins_dir
.clone()
.unwrap_or_else(|| self.config.storage_path.join(".plugins"));
// Resolve the log root to a sibling of the plugins dir by default
// so an uninstall never wipes another plugin's logs, and pass it
// into the manager via the config it owns.
let mut plugin_config = self.config.plugins.clone();
if plugin_config.log_dir.is_none() {
plugin_config.log_dir = Some(self.config.storage_path.join(".plugin-logs"));
}
let manager = Arc::new(
crate::infrastructure::services::plugins::ExtismPluginManager::load_from_dir(
self.config.plugins.clone(),
plugin_config,
&dir,
),
);
@@ -624,11 +633,23 @@ impl AppServiceFactory {
loaded = manager.loaded_count(),
"plugin manager initialized"
);
let dispatch: Arc<dyn crate::application::ports::plugin_ports::PluginDispatchPort> =
manager.clone();
let management: Arc<
dyn crate::application::ports::plugin_ports::PluginManagementPort,
> = manager;
> = manager.clone();
// Background maintenance: prune each plugin's rotated log
// segments by age + aggregate size on a schedule. Depends only on
// the log store + the management port, so no special ordering.
crate::infrastructure::services::plugins::PluginLogMaintenanceService::new(
manager.log_store(),
management.clone(),
6, // hours between sweeps
)
.start();
return (Some(dispatch), Some(management));
}
}