Skip to content

Commit 6d4fa61

Browse files
committed
feat: Implement automatic log rotation
1 parent 9b42908 commit 6d4fa61

3 files changed

Lines changed: 81 additions & 10 deletions

File tree

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ This stage will implement logging of all operations in ArgusDB to allow for faul
2121
- [x] Define a log file format in specs/logging.md
2222
- [x] Add log entries whenever new documents are inserted
2323
- [x] Implement log recovery to read the log on restart and recover from a crash
24-
- [] Implement log rotation and deletion of old log files
24+
- [x] Implement log rotation and deletion of old log files
2525

2626
# Stage 4 - Disk storage
2727

src/log.rs

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use chrono::{DateTime, Utc};
22
use serde::{Deserialize, Serialize};
33
use serde_json::Value;
4-
use std::fs::OpenOptions;
4+
use std::fs::{self, OpenOptions};
55
use std::io::Write;
6-
use std::path::Path;
6+
use std::path::{Path, PathBuf};
77

88
#[derive(Serialize, Deserialize, Debug)]
99
pub enum Operation {
@@ -28,24 +28,72 @@ pub struct LogEntry {
2828

2929
pub struct Logger {
3030
file: std::fs::File,
31+
path: PathBuf,
32+
rotation_threshold: u64,
3133
}
3234

3335
impl Logger {
34-
pub fn new<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
36+
pub fn new<P: AsRef<Path>>(path: P, rotation_threshold: u64) -> std::io::Result<Self> {
37+
let path = path.as_ref().to_path_buf();
3538
let file = OpenOptions::new()
3639
.create(true)
3740
.write(true)
3841
.append(true)
39-
.open(path)?;
40-
Ok(Logger { file })
42+
.open(&path)?;
43+
Ok(Logger {
44+
file,
45+
path,
46+
rotation_threshold,
47+
})
4148
}
4249

4350
pub fn log(&mut self, op: Operation) -> std::io::Result<()> {
51+
if self.file.metadata()?.len() > self.rotation_threshold {
52+
self.rotate()?;
53+
}
4454
let entry = LogEntry {
4555
ts: Utc::now(),
4656
op,
4757
};
4858
let json = serde_json::to_string(&entry)?;
4959
writeln!(self.file, "{}", json)
5060
}
61+
62+
pub fn rotate(&mut self) -> std::io::Result<()> {
63+
let new_path = self.path.with_extension("log.1");
64+
fs::rename(&self.path, new_path)?;
65+
self.file = OpenOptions::new()
66+
.create(true)
67+
.write(true)
68+
.append(true)
69+
.open(&self.path)?;
70+
Ok(())
71+
}
72+
}
73+
74+
#[cfg(test)]
75+
mod tests {
76+
use super::*;
77+
use serde_json::json;
78+
use tempfile::NamedTempFile;
79+
80+
#[test]
81+
fn test_log_rotate() {
82+
let log_file = NamedTempFile::new().unwrap();
83+
let mut logger = Logger::new(log_file.path(), 1024 * 1024).unwrap();
84+
let op = Operation::Insert {
85+
id: "test-id".to_string(),
86+
doc: json!({"a": 1}),
87+
};
88+
logger.log(op).unwrap();
89+
90+
logger.rotate().unwrap();
91+
92+
let log_content = std::fs::read_to_string(log_file.path()).unwrap();
93+
assert!(log_content.is_empty());
94+
95+
let rotated_log_path = log_file.path().with_extension("log.1");
96+
let rotated_log_content = std::fs::read_to_string(rotated_log_path).unwrap();
97+
assert!(!rotated_log_content.is_empty());
98+
}
5199
}

src/storage.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ pub struct MemTable {
1111
}
1212

1313
impl MemTable {
14-
pub fn new(log_path: &str) -> Self {
15-
let logger = Logger::new(log_path).unwrap();
14+
pub fn new(log_path: &str, rotation_threshold: u64) -> Self {
15+
let logger = Logger::new(log_path, rotation_threshold).unwrap();
1616
let mut memtable = MemTable {
1717
documents: BTreeMap::new(),
1818
schema: Schema {
@@ -171,7 +171,7 @@ mod tests {
171171

172172
fn create_test_memtable() -> (NamedTempFile, MemTable) {
173173
let log_file = NamedTempFile::new().unwrap();
174-
let memtable = MemTable::new(log_file.path().to_str().unwrap());
174+
let memtable = MemTable::new(log_file.path().to_str().unwrap(), 1024 * 1024);
175175
(log_file, memtable)
176176
}
177177

@@ -266,8 +266,31 @@ mod tests {
266266

267267
memtable.delete(&id1);
268268

269-
let memtable2 = MemTable::new(log_file.path().to_str().unwrap());
269+
let memtable2 = MemTable::new(log_file.path().to_str().unwrap(), 1024 * 1024);
270270
assert_eq!(memtable2.documents.len(), 1);
271271
assert_eq!(*memtable2.documents.get(&id2).unwrap(), doc2);
272272
}
273+
274+
#[test]
275+
fn test_automatic_log_rotation() {
276+
let log_file = NamedTempFile::new().unwrap();
277+
let mut memtable = MemTable::new(log_file.path().to_str().unwrap(), 100);
278+
let doc1 = json!({"a": 1});
279+
memtable.insert(doc1);
280+
281+
let log_content = std::fs::read_to_string(log_file.path()).unwrap();
282+
assert!(!log_content.is_empty());
283+
284+
let doc2 = json!({"b": "a long string to make the log entry bigger than the threshold"});
285+
memtable.insert(doc2);
286+
287+
let log_content_after_rotation = std::fs::read_to_string(log_file.path()).unwrap();
288+
let rotated_log_path = log_file.path().with_extension("log.1");
289+
let rotated_log_content = std::fs::read_to_string(rotated_log_path).unwrap();
290+
291+
assert!(!rotated_log_content.is_empty());
292+
assert!(rotated_log_content.contains("{\"a\":1}"));
293+
assert!(!log_content_after_rotation.contains("{\"a\":1}"));
294+
assert!(log_content_after_rotation.contains("a long string"));
295+
}
273296
}

0 commit comments

Comments
 (0)