Add comprehensive logging with tracing, file rotation, and systemd integration
- Add tracing-appender and tracing-journald for production logging - Add LoggingConfig with trace_sample_rate, json_output, journald options - Expand init_logging() with file rotation, journald, and stderr layers - Add sanitize_path() helper for PII protection in logs - Instrument FUSE operations with #[instrument] and trace decision points - Instrument gRPC handlers (10 methods) with span correlation - Add spawn instrumentation for health monitor, indexer, watcher tasks - Add broadcast lag handling (RecvError::Lagged) in event subscribers - Fix webhook.rs expect() calls with proper error handling - Add logging to patterns.rs, collections.rs, artwork.rs database ops - Add Drop impl logging for PluginManager and WatchHandle - Update systemd service with rate limiting and journal output - Add logrotate config and example config.toml with logging section
This commit is contained in:
@@ -5,7 +5,7 @@ use musicfs_metadata::artwork::{ArtSize, ArtType, Artwork};
|
||||
use std::io::Cursor;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use tracing::debug;
|
||||
use tracing::{debug, info, trace, warn};
|
||||
|
||||
const MAX_ARTWORK_INPUT_SIZE: usize = 10 * 1024 * 1024;
|
||||
|
||||
@@ -40,6 +40,7 @@ impl ArtworkCache {
|
||||
[],
|
||||
)?;
|
||||
|
||||
info!(path = ?db_path, "Artwork cache opened");
|
||||
Ok(Self {
|
||||
store,
|
||||
db_path: db_path.to_path_buf(),
|
||||
@@ -47,7 +48,9 @@ impl ArtworkCache {
|
||||
}
|
||||
|
||||
pub async fn store(&self, file_id: i64, artwork: &Artwork) -> Result<ChunkHash, ArtworkError> {
|
||||
trace!(file_id = file_id, size_bytes = artwork.data.len(), "Storing artwork");
|
||||
if artwork.data.len() > MAX_ARTWORK_INPUT_SIZE {
|
||||
warn!(file_id = file_id, size = artwork.data.len(), max = MAX_ARTWORK_INPUT_SIZE, "Artwork too large");
|
||||
return Err(ArtworkError::ImageTooLarge(artwork.data.len()));
|
||||
}
|
||||
|
||||
@@ -88,6 +91,7 @@ impl ArtworkCache {
|
||||
art_type: &str,
|
||||
size: ArtSize,
|
||||
) -> Result<Option<Vec<u8>>, ArtworkError> {
|
||||
trace!(file_id = file_id, art_type = %art_type, "Getting artwork");
|
||||
let db_path = self.db_path.clone();
|
||||
let art_type_clone = art_type.to_string();
|
||||
|
||||
@@ -107,6 +111,7 @@ impl ArtworkCache {
|
||||
|
||||
match hash_hex {
|
||||
Some(hex) => {
|
||||
trace!(file_id = file_id, "Artwork cache hit");
|
||||
let hash = ChunkHash::from_hex(&hex).ok_or(ArtworkError::InvalidHash)?;
|
||||
let data = self.store.get(&hash).await?;
|
||||
|
||||
@@ -118,7 +123,10 @@ impl ArtworkCache {
|
||||
}
|
||||
}
|
||||
}
|
||||
None => Ok(None),
|
||||
None => {
|
||||
trace!(file_id = file_id, "Artwork cache miss");
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ pub struct Database {
|
||||
|
||||
impl Database {
|
||||
pub fn open(path: &Path) -> Result<Self> {
|
||||
info!(?path, "Opening database");
|
||||
debug!(?path, "Opening database");
|
||||
|
||||
let conn =
|
||||
Connection::open(path).map_err(|e| Error::Database(format!("open failed: {}", e)))?;
|
||||
@@ -24,9 +24,12 @@ impl Database {
|
||||
conn.execute_batch(SCHEMA)
|
||||
.map_err(|e| Error::Database(format!("schema init failed: {}", e)))?;
|
||||
|
||||
Ok(Self {
|
||||
let db = Self {
|
||||
conn: Arc::new(Mutex::new(conn)),
|
||||
})
|
||||
};
|
||||
let count = db.file_count().unwrap_or(0);
|
||||
info!(path = ?path, file_count = count, "Database opened");
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
pub fn open_memory() -> Result<Self> {
|
||||
|
||||
@@ -3,6 +3,7 @@ use musicfs_core::{AudioMeta, FileMeta, OriginId, Result, VirtualPath};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
use tracing::trace;
|
||||
|
||||
pub struct MetadataCache {
|
||||
db: Arc<Database>,
|
||||
@@ -34,7 +35,10 @@ impl MetadataCache {
|
||||
}
|
||||
|
||||
pub fn lookup(&self, path: &VirtualPath) -> Result<Option<FileMeta>> {
|
||||
self.db.get_file_by_virtual_path(path)
|
||||
let result = self.db.get_file_by_virtual_path(path)?;
|
||||
let hit = result.is_some();
|
||||
trace!(path = path.as_str(), hit, "metadata cache lookup");
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn is_fresh(
|
||||
@@ -52,8 +56,11 @@ impl MetadataCache {
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or(Duration::ZERO)
|
||||
.as_secs();
|
||||
Ok(current_secs == cached_secs)
|
||||
let hit = current_secs == cached_secs;
|
||||
trace!(path = ?real_path, hit, "metadata freshness check");
|
||||
Ok(hit)
|
||||
} else {
|
||||
trace!(path = ?real_path, hit = false, "metadata freshness check");
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use parking_lot::{Mutex, RwLock};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use tracing::{debug, info, trace};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AccessPattern {
|
||||
@@ -79,15 +80,19 @@ impl PatternStore {
|
||||
map
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
let store = Self {
|
||||
db: Mutex::new(db),
|
||||
sequence_counts: RwLock::new(sequence_counts),
|
||||
time_patterns: RwLock::new(HashMap::new()),
|
||||
max_history,
|
||||
})
|
||||
};
|
||||
let sequence_count = store.sequence_counts.read().len();
|
||||
info!(path = ?db_path, sequence_count = sequence_count, max_history = max_history, "Pattern store opened");
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
pub fn record(&self, file_id: FileId, _context: AccessContext) -> Result<(), PatternError> {
|
||||
trace!(file_id = file_id.0, "Recording access pattern");
|
||||
let now = SystemTime::now();
|
||||
let timestamp = now.duration_since(UNIX_EPOCH).unwrap().as_secs() as i64;
|
||||
let hour = (timestamp / 3600 % 24) as u8;
|
||||
@@ -144,11 +149,13 @@ impl PatternStore {
|
||||
.collect();
|
||||
|
||||
predictions.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
predictions
|
||||
let result: Vec<FileId> = predictions
|
||||
.into_iter()
|
||||
.take(limit)
|
||||
.map(|(id, _)| id)
|
||||
.collect()
|
||||
.collect();
|
||||
debug!(file_id = current.0, predictions = result.len(), "Predicted next files");
|
||||
result
|
||||
}
|
||||
|
||||
pub fn predict_for_time(&self, hour: u8, limit: usize) -> Vec<FileId> {
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::ffi::{OsStr, OsString};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::RwLock;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use tracing::{debug, trace};
|
||||
|
||||
pub type Inode = u64;
|
||||
pub const ROOT_INODE: Inode = 1;
|
||||
@@ -123,8 +124,12 @@ impl VirtualTree {
|
||||
|
||||
pub fn lookup(&self, parent_inode: Inode, name: &OsStr) -> Option<Inode> {
|
||||
if let Some(VirtualNode::Directory(dir)) = self.nodes.get(&parent_inode) {
|
||||
dir.children.get(name).copied()
|
||||
let result = dir.children.get(name).copied();
|
||||
let hit = result.is_some();
|
||||
trace!(inode = parent_inode, name = ?name, hit, "tree lookup");
|
||||
result
|
||||
} else {
|
||||
trace!(inode = parent_inode, name = ?name, hit = false, "tree lookup");
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -194,6 +199,7 @@ impl VirtualTree {
|
||||
dir.children.insert(name, inode);
|
||||
}
|
||||
|
||||
debug!(inode, path = path.as_str(), file_id = ?meta.id, "add file to tree");
|
||||
inode
|
||||
}
|
||||
|
||||
@@ -263,6 +269,7 @@ impl VirtualTree {
|
||||
}
|
||||
}
|
||||
|
||||
debug!(inode, path = path.as_str(), file_id = ?file.file_id, "remove file from tree");
|
||||
Some(file.file_id)
|
||||
} else {
|
||||
None
|
||||
|
||||
Reference in New Issue
Block a user