x_log4rs_sqlite/
lib.rs

1use anyhow::anyhow;
2use std::sync::Arc;
3use std::sync::RwLock;
4
5struct SqliteLogAppender {
6    buf: Arc<RwLock<Vec<LogRecord>>>,
7    buf_size: usize,
8    file_name: String,
9}
10
11struct LogRecord {
12    id: String,
13    level: String,
14    ts: String,
15    message: String,
16}
17
18impl SqliteLogAppender {
19    pub fn new(buf_size: usize, file_name: &str) -> anyhow::Result<SqliteLogAppender> {
20        Ok(SqliteLogAppender {
21            buf: Arc::new(RwLock::new(Vec::new())),
22            buf_size,
23            file_name: file_name.to_string(),
24        })
25    }
26    fn create_entry_table_if_not_exists(conn: &rusqlite::Connection) -> anyhow::Result<()> {
27        let table_sql = "create table if not exists entry (
28            id varchar(128) not null primary key,
29            ts varchar(128) not null,
30            level varchar(128) not null,
31            message varchar(8192) not null
32        )";
33        let index_ts_sql = "create index if not exists entry_ts_i on entry (ts)";
34        conn.execute(table_sql, [])?;
35        conn.execute(index_ts_sql, [])?;
36        Ok(())
37    }
38    fn connect(&self) -> anyhow::Result<rusqlite::Connection> {
39        let conn = rusqlite::Connection::open(&self.file_name)?;
40        SqliteLogAppender::create_entry_table_if_not_exists(&conn)?;
41        Ok(conn)
42    }
43    fn maybe_flush_buf(&self, buf_lock: &mut Vec<LogRecord>) -> anyhow::Result<()> {
44        if buf_lock.len() < self.buf_size {
45            return Ok(());
46        }
47        self.flush_buf(buf_lock)?;
48        Ok(())
49    }
50    fn flush_buf(&self, buf_lock: &mut Vec<LogRecord>) -> anyhow::Result<()> {
51        let mut conn = self.connect()?;
52        let tx = conn.transaction()?;
53        {
54            let mut stmt =
55                tx.prepare("insert into entry (id, ts, level, message) values (?1, ?2, ?3, ?4)")?;
56            for lr in buf_lock.iter() {
57                stmt.execute([&lr.id, &lr.ts, &lr.level, &lr.message])?;
58            }
59        }
60        tx.commit()?;
61        buf_lock.clear();
62        Ok(())
63    }
64}
65
66impl std::fmt::Debug for SqliteLogAppender {
67    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
68        f.debug_struct("SqliteLogAppender")
69            .field("buf_size", &self.buf_size)
70            .field("file_name", &self.file_name)
71            .finish()
72    }
73}
74
75impl log4rs::append::Append for SqliteLogAppender {
76    fn append(&self, record: &log::Record) -> anyhow::Result<()> {
77        let lr = LogRecord {
78            id: uuid::Uuid::new_v4().to_string(),
79            level: record.level().as_str().to_string(),
80            ts: chrono::Utc::now()
81                .format("%Y-%m-%d %H:%M:%S%.6f")
82                .to_string(),
83            message: record.args().to_string(),
84        };
85        let mut buf_lock = self
86            .buf
87            .write()
88            .map_err(|e| anyhow!("Error locking buf: {}", e))?;
89        buf_lock.push(lr);
90        self.maybe_flush_buf(&mut buf_lock)?;
91        Ok(())
92    }
93    fn flush(&self) {
94        let mut buf_lock = self.buf.write().expect("Error locking buf");
95        self.flush_buf(&mut buf_lock).expect("Error flushing buf");
96    }
97}
98
99#[derive(Clone, Debug, Default, serde::Deserialize)]
100#[serde(deny_unknown_fields)]
101pub struct SqliteLogAppenderConfig {
102    path: String,
103}
104
105pub struct SqliteLogAppenderDeserializer {}
106
107impl log4rs::config::Deserialize for SqliteLogAppenderDeserializer {
108    type Trait = dyn log4rs::append::Append;
109    type Config = SqliteLogAppenderConfig;
110
111    fn deserialize(
112        &self,
113        config: SqliteLogAppenderConfig,
114        _: &log4rs::config::Deserializers,
115    ) -> anyhow::Result<Box<dyn log4rs::append::Append>> {
116        Ok(Box::new(SqliteLogAppender::new(1024, &config.path)?))
117    }
118}