Skip to content

Commit be35de9

Browse files
committed
feat: Add /logs route to view logs
1 parent 6cc5649 commit be35de9

File tree

6 files changed

+96
-3
lines changed

6 files changed

+96
-3
lines changed

src/database/operation.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use chrono::{DateTime, Utc};
2+
use derive_more::Display;
23
use serde::{Deserialize, Serialize};
34

45
use crate::extractors::user::User;
56
use crate::routes::category::CategoryForm;
67

78
/// Type of operation applied to the database.
8-
#[derive(Clone, Debug, Serialize, Deserialize)]
9+
#[derive(Clone, Debug, Display, Serialize, Deserialize)]
910
pub enum OperationType {
1011
Create,
1112
Update,
@@ -18,7 +19,7 @@ pub struct OperationId {
1819
pub name: String,
1920
}
2021

21-
#[derive(Clone, Debug, Serialize, Deserialize)]
22+
#[derive(Clone, Debug, Display, Serialize, Deserialize)]
2223
pub enum Table {
2324
Category,
2425
}
@@ -32,6 +33,12 @@ pub enum Operation {
3233
Category(CategoryForm),
3334
}
3435

36+
impl std::fmt::Display for Operation {
37+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38+
write!(f, "{}", &serde_json::to_string(self).unwrap())
39+
}
40+
}
41+
3542
#[derive(Clone, Debug, Serialize, Deserialize)]
3643
pub struct OperationLog {
3744
pub user: Option<User>,

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub fn router(state: state::AppState) -> Router {
2222
.route("/progress/{view_request}", get(routes::progress::progress))
2323
.route("/category", get(routes::category::index))
2424
.route("/category", post(routes::category::create))
25+
.route("/logs", get(routes::logs::index))
2526
// Register static assets routes
2627
.nest("/assets", static_router())
2728
// Insert request timing

src/routes/logs.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use askama::Template;
2+
use askama_web::WebTemplate;
3+
use axum::extract::State;
4+
5+
use crate::database::operation::OperationLog;
6+
use crate::extractors::user::User;
7+
use crate::state::{AppState, AppStateContext, error::AppStateError};
8+
9+
#[derive(Template, WebTemplate)]
10+
#[template(path = "logs.html")]
11+
pub struct LogTemplate {
12+
pub state: AppStateContext,
13+
pub logs: Vec<OperationLog>,
14+
pub user: Option<User>,
15+
}
16+
17+
pub async fn index(
18+
State(app_state): State<AppState>,
19+
user: Option<User>,
20+
) -> Result<LogTemplate, AppStateError> {
21+
let app_state_context = app_state.context().await?;
22+
let logs = app_state.logger.read().await?;
23+
24+
Ok(LogTemplate {
25+
state: app_state_context,
26+
logs,
27+
user,
28+
})
29+
}

src/routes/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub mod category;
22
pub mod index;
3+
pub mod logs;
34
pub mod progress;

src/state/logger.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use camino::Utf8PathBuf;
22
use snafu::prelude::*;
33
use tokio::fs::{File, OpenOptions};
4-
use tokio::io::AsyncWriteExt;
4+
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
55
use tokio::sync::RwLock;
66

7+
use std::io::SeekFrom;
78
use std::sync::Arc;
89

910
use crate::database::operation::OperationLog;
@@ -35,6 +36,11 @@ pub enum LoggerError {
3536
path: Utf8PathBuf,
3637
source: serde_json::Error,
3738
},
39+
#[snafu(display("Other IO error with operaitons log {path}"))]
40+
IO {
41+
path: Utf8PathBuf,
42+
source: std::io::Error,
43+
},
3844
}
3945

4046
#[derive(Clone, Debug)]
@@ -84,4 +90,39 @@ impl Logger {
8490

8591
Ok(())
8692
}
93+
94+
pub async fn read(&self) -> Result<Vec<OperationLog>, AppStateError> {
95+
// When in append mode, the cursor may be set to the end of file
96+
// so start again from the beginning
97+
let mut s = String::new();
98+
{
99+
let mut handle = self.handle.write().await;
100+
handle
101+
.seek(SeekFrom::Start(0))
102+
.await
103+
.context(IOSnafu {
104+
path: self.path.to_path_buf(),
105+
})
106+
.context(state_error::LoggerSnafu)?;
107+
108+
handle
109+
.read_to_string(&mut s)
110+
.await
111+
.context(ReadSnafu {
112+
path: self.path.to_path_buf(),
113+
})
114+
.context(state_error::LoggerSnafu)?;
115+
}
116+
117+
// Now that we have dropped the RwLock, parse the results
118+
s.lines()
119+
.map(|entry| {
120+
serde_json::from_str(entry)
121+
.context(ParseSnafu {
122+
path: self.path.to_path_buf(),
123+
})
124+
.context(state_error::LoggerSnafu)
125+
})
126+
.collect()
127+
}
87128
}

templates/logs.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{% extends "base.html" %}
2+
3+
{% block main %}
4+
<h1 class="title" style="text-align: center;">Logs</h1>
5+
6+
{% for log in logs %}
7+
<details>
8+
<summary>
9+
{{ log.date }} {% if let Some(log_user) = log.user %}{{ log_user }}{% endif %}{{ log.operation }} on table {{ log.table }}: {{ log.operation_id.name }} ({{ log.operation_id.object_id }})
10+
</summary>
11+
{{ log.operation_form }}
12+
</details>
13+
{% endfor %}
14+
{% endblock %}

0 commit comments

Comments
 (0)