Skip to content

Commit dbd1ff5

Browse files
authored
Merge pull request #100 from Tuntii/perf/audit-file-store-async-io-14806398196114527397
⚡ Optimize FileAuditStore::log_async to use spawn_blocking
2 parents eea4bce + ea82b0f commit dbd1ff5

1 file changed

Lines changed: 44 additions & 14 deletions

File tree

crates/rustapi-extras/src/audit/file_store.rs

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ use super::event::AuditEvent;
44
use super::query::AuditQuery;
55
use super::store::{AuditError, AuditResult, AuditStore};
66
use std::fs::{File, OpenOptions};
7+
use std::future::Future;
78
use std::io::{BufRead, BufReader, Write};
89
use std::path::PathBuf;
9-
use std::sync::Mutex;
10+
use std::pin::Pin;
11+
use std::sync::{Arc, Mutex};
1012

1113
/// Configuration for file-based audit store.
1214
#[derive(Debug, Clone)]
@@ -45,19 +47,29 @@ impl FileAuditStoreConfig {
4547
}
4648
}
4749

48-
/// File-based audit store (JSON Lines format).
49-
pub struct FileAuditStore {
50+
/// Internal state for FileAuditStore
51+
#[derive(Debug)]
52+
struct FileAuditStoreInner {
5053
config: FileAuditStoreConfig,
5154
writer: Mutex<Option<File>>,
5255
}
5356

57+
/// File-based audit store (JSON Lines format).
58+
#[derive(Clone, Debug)]
59+
pub struct FileAuditStore {
60+
inner: Arc<FileAuditStoreInner>,
61+
}
62+
5463
impl FileAuditStore {
5564
/// Create a new file-based audit store.
5665
pub fn new(config: FileAuditStoreConfig) -> AuditResult<Self> {
57-
let store = Self {
66+
let inner = FileAuditStoreInner {
5867
config,
5968
writer: Mutex::new(None),
6069
};
70+
let store = Self {
71+
inner: Arc::new(inner),
72+
};
6173
store.open_writer()?;
6274
Ok(store)
6375
}
@@ -70,24 +82,25 @@ impl FileAuditStore {
7082
/// Open or create the file writer.
7183
fn open_writer(&self) -> AuditResult<()> {
7284
let mut writer = self
85+
.inner
7386
.writer
7487
.lock()
7588
.map_err(|e| AuditError::WriteError(format!("Failed to acquire lock: {}", e)))?;
7689

7790
// Create parent directories if they don't exist
78-
if let Some(parent) = self.config.file_path.parent() {
79-
if !parent.exists() && self.config.create_if_missing {
91+
if let Some(parent) = self.inner.config.file_path.parent() {
92+
if !parent.exists() && self.inner.config.create_if_missing {
8093
std::fs::create_dir_all(parent).map_err(|e| {
8194
AuditError::IoError(format!("Failed to create directories: {}", e))
8295
})?;
8396
}
8497
}
8598

8699
let file = OpenOptions::new()
87-
.create(self.config.create_if_missing)
88-
.append(self.config.append)
100+
.create(self.inner.config.create_if_missing)
101+
.append(self.inner.config.append)
89102
.write(true)
90-
.open(&self.config.file_path)
103+
.open(&self.inner.config.file_path)
91104
.map_err(|e| AuditError::IoError(format!("Failed to open file: {}", e)))?;
92105

93106
*writer = Some(file);
@@ -96,8 +109,8 @@ impl FileAuditStore {
96109

97110
/// Check if rotation is needed and perform it.
98111
fn check_rotation(&self) -> AuditResult<()> {
99-
if let Some(max_size) = self.config.max_file_size {
100-
if let Ok(metadata) = std::fs::metadata(&self.config.file_path) {
112+
if let Some(max_size) = self.inner.config.max_file_size {
113+
if let Ok(metadata) = std::fs::metadata(&self.inner.config.file_path) {
101114
if metadata.len() >= max_size {
102115
self.rotate()?;
103116
}
@@ -109,6 +122,7 @@ impl FileAuditStore {
109122
/// Rotate the log file.
110123
fn rotate(&self) -> AuditResult<()> {
111124
let mut writer = self
125+
.inner
112126
.writer
113127
.lock()
114128
.map_err(|e| AuditError::WriteError(format!("Failed to acquire lock: {}", e)))?;
@@ -123,12 +137,13 @@ impl FileAuditStore {
123137
.unwrap_or(0);
124138

125139
let rotated_path = self
140+
.inner
126141
.config
127142
.file_path
128143
.with_extension(format!("{}.log", timestamp));
129144

130145
// Rename current file
131-
std::fs::rename(&self.config.file_path, &rotated_path)
146+
std::fs::rename(&self.inner.config.file_path, &rotated_path)
132147
.map_err(|e| AuditError::IoError(format!("Failed to rotate file: {}", e)))?;
133148

134149
// Open new file
@@ -140,7 +155,7 @@ impl FileAuditStore {
140155

141156
/// Read all events from the file.
142157
fn read_all_events(&self) -> AuditResult<Vec<AuditEvent>> {
143-
let path = &self.config.file_path;
158+
let path = &self.inner.config.file_path;
144159

145160
if !path.exists() {
146161
return Ok(Vec::new());
@@ -178,6 +193,7 @@ impl AuditStore for FileAuditStore {
178193
self.check_rotation()?;
179194

180195
let mut writer = self
196+
.inner
181197
.writer
182198
.lock()
183199
.map_err(|e| AuditError::WriteError(format!("Failed to acquire lock: {}", e)))?;
@@ -195,6 +211,18 @@ impl AuditStore for FileAuditStore {
195211
Ok(())
196212
}
197213

214+
fn log_async(
215+
&self,
216+
event: AuditEvent,
217+
) -> Pin<Box<dyn Future<Output = AuditResult<()>> + Send + '_>> {
218+
let store = self.clone();
219+
Box::pin(async move {
220+
tokio::task::spawn_blocking(move || store.log(event))
221+
.await
222+
.map_err(|e| AuditError::IoError(format!("Task join error: {}", e)))?
223+
})
224+
}
225+
198226
fn get(&self, id: &str) -> AuditResult<Option<AuditEvent>> {
199227
let events = self.read_all_events()?;
200228
Ok(events.into_iter().find(|e| e.id == id))
@@ -238,14 +266,15 @@ impl AuditStore for FileAuditStore {
238266

239267
fn clear(&self) -> AuditResult<()> {
240268
let mut writer = self
269+
.inner
241270
.writer
242271
.lock()
243272
.map_err(|e| AuditError::WriteError(format!("Failed to acquire lock: {}", e)))?;
244273

245274
*writer = None;
246275

247276
// Truncate the file
248-
File::create(&self.config.file_path)
277+
File::create(&self.inner.config.file_path)
249278
.map_err(|e| AuditError::IoError(format!("Failed to clear file: {}", e)))?;
250279

251280
// Reopen
@@ -257,6 +286,7 @@ impl AuditStore for FileAuditStore {
257286

258287
fn flush(&self) -> AuditResult<()> {
259288
let mut writer = self
289+
.inner
260290
.writer
261291
.lock()
262292
.map_err(|e| AuditError::WriteError(format!("Failed to acquire lock: {}", e)))?;

0 commit comments

Comments
 (0)