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}