Add logging functionality to the dev panel
This commit is contained in:
parent
c8dd82a5e0
commit
812ba9dc3b
6 changed files with 141 additions and 38 deletions
|
|
@ -187,6 +187,12 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#dev-panel {
|
||||||
|
.log-line-odd {
|
||||||
|
background-color: consts.$color-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use askama::Template;
|
||||||
use crate::{
|
use crate::{
|
||||||
Context,
|
Context,
|
||||||
data::{db, model},
|
data::{db, model},
|
||||||
|
log::Log,
|
||||||
translation::{self, Sentence, Tr},
|
translation::{self, Sentence, Tr},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -58,6 +59,8 @@ pub struct HomeTemplate {
|
||||||
pub struct DevPanelTemplate {
|
pub struct DevPanelTemplate {
|
||||||
pub context: Context,
|
pub context: Context,
|
||||||
pub recipes: Recipes,
|
pub recipes: Recipes,
|
||||||
|
pub log: Log,
|
||||||
|
pub current_log_file: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
use std::{fs, path::Path};
|
use std::{
|
||||||
|
fs::{self, File},
|
||||||
|
io::{BufRead, BufReader},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
use tracing::{Level, event};
|
||||||
use tracing_appender::{
|
use tracing_appender::{
|
||||||
non_blocking::WorkerGuard,
|
non_blocking::WorkerGuard,
|
||||||
rolling::{RollingFileAppender, Rotation},
|
rolling::{RollingFileAppender, Rotation},
|
||||||
|
|
@ -22,38 +29,82 @@ const TRACING_LEVEL: tracing::Level = tracing::Level::INFO;
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
const TRACING_DISPLAY_THREAD: bool = false;
|
const TRACING_DISPLAY_THREAD: bool = false;
|
||||||
|
|
||||||
pub fn init<P>(directory: P) -> WorkerGuard
|
#[derive(Clone)]
|
||||||
where
|
pub struct Log {
|
||||||
P: AsRef<Path>,
|
guard: Arc<WorkerGuard>,
|
||||||
{
|
directory: PathBuf,
|
||||||
if !directory.as_ref().exists() {
|
}
|
||||||
fs::DirBuilder::new().create(&directory).unwrap();
|
|
||||||
|
// TODO: Remove all 'unwrap'.
|
||||||
|
impl Log {
|
||||||
|
pub fn new<P>(directory: P) -> Self
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
if !directory.as_ref().exists() {
|
||||||
|
fs::DirBuilder::new().create(&directory).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_appender = RollingFileAppender::builder()
|
||||||
|
.rotation(Rotation::DAILY)
|
||||||
|
.filename_prefix(consts::BASE_LOG_FILENAME)
|
||||||
|
.filename_suffix("log")
|
||||||
|
.build(&directory)
|
||||||
|
.expect("Initializing rolling file appender failed");
|
||||||
|
|
||||||
|
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
|
||||||
|
|
||||||
|
let layer_file = tracing_subscriber::fmt::layer()
|
||||||
|
.with_writer(non_blocking.with_max_level(TRACING_LEVEL))
|
||||||
|
.with_ansi(false)
|
||||||
|
.with_thread_ids(TRACING_DISPLAY_THREAD)
|
||||||
|
.with_thread_names(TRACING_DISPLAY_THREAD);
|
||||||
|
|
||||||
|
let layer_stdout = tracing_subscriber::fmt::layer()
|
||||||
|
.with_writer(std::io::stdout.with_max_level(TRACING_LEVEL))
|
||||||
|
.with_thread_ids(TRACING_DISPLAY_THREAD)
|
||||||
|
.with_thread_names(TRACING_DISPLAY_THREAD);
|
||||||
|
|
||||||
|
tracing_subscriber::Registry::default()
|
||||||
|
.with(layer_file)
|
||||||
|
.with(layer_stdout)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
Log {
|
||||||
|
guard: Arc::new(guard),
|
||||||
|
directory: directory.as_ref().to_path_buf(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let file_appender = RollingFileAppender::builder()
|
pub fn file_names(&self) -> std::io::Result<Vec<String>> {
|
||||||
.rotation(Rotation::DAILY)
|
Ok(self
|
||||||
.filename_prefix(consts::BASE_LOG_FILENAME)
|
.directory
|
||||||
.filename_suffix("log")
|
.read_dir()?
|
||||||
.build(directory)
|
.map(|entry| {
|
||||||
.expect("Initializing rolling file appender failed");
|
entry
|
||||||
|
.unwrap()
|
||||||
|
.path()
|
||||||
|
.file_name()
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
})
|
||||||
|
.sorted()
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
|
pub fn read_content(&self, filename: &str) -> std::io::Result<Vec<String>> {
|
||||||
|
let filepath = self.directory.join(filename);
|
||||||
let layer_file = tracing_subscriber::fmt::layer()
|
if filepath.is_file() {
|
||||||
.with_writer(non_blocking.with_max_level(TRACING_LEVEL))
|
let file = File::open(filepath)?;
|
||||||
.with_ansi(false)
|
// tracing::event!(Level::ERROR, "file: {:?}", file);
|
||||||
.with_thread_ids(TRACING_DISPLAY_THREAD)
|
Ok(BufReader::new(file)
|
||||||
.with_thread_names(TRACING_DISPLAY_THREAD);
|
.lines()
|
||||||
|
.map(|l| l.unwrap_or_default())
|
||||||
let layer_stdout = tracing_subscriber::fmt::layer()
|
.collect())
|
||||||
.with_writer(std::io::stdout.with_max_level(TRACING_LEVEL))
|
} else {
|
||||||
.with_thread_ids(TRACING_DISPLAY_THREAD)
|
Ok(vec![])
|
||||||
.with_thread_names(TRACING_DISPLAY_THREAD);
|
}
|
||||||
|
}
|
||||||
tracing_subscriber::Registry::default()
|
|
||||||
.with(layer_file)
|
|
||||||
.with(layer_stdout)
|
|
||||||
.init();
|
|
||||||
|
|
||||||
guard
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ use tower_http::{
|
||||||
use tracing::{Level, event};
|
use tracing::{Level, event};
|
||||||
|
|
||||||
use data::{backup, db, model};
|
use data::{backup, db, model};
|
||||||
|
use log::Log;
|
||||||
use translation::Tr;
|
use translation::Tr;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
|
@ -45,6 +46,7 @@ mod utils;
|
||||||
struct AppState {
|
struct AppState {
|
||||||
config: Config,
|
config: Config,
|
||||||
db_connection: db::Connection,
|
db_connection: db::Connection,
|
||||||
|
log: Log,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromRef<AppState> for Config {
|
impl FromRef<AppState> for Config {
|
||||||
|
|
@ -59,6 +61,12 @@ impl FromRef<AppState> for db::Connection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromRef<AppState> for Log {
|
||||||
|
fn from_ref(app_state: &AppState) -> Log {
|
||||||
|
app_state.log.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl axum::response::IntoResponse for db::DBError {
|
impl axum::response::IntoResponse for db::DBError {
|
||||||
fn into_response(self) -> Response {
|
fn into_response(self) -> Response {
|
||||||
ron_utils::ron_error(StatusCode::INTERNAL_SERVER_ERROR, &self.to_string()).into_response()
|
ron_utils::ron_error(StatusCode::INTERNAL_SERVER_ERROR, &self.to_string()).into_response()
|
||||||
|
|
@ -103,7 +111,7 @@ impl Context {
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let config = config::load();
|
let config = config::load();
|
||||||
let _guard = log::init(&config.logs_directory);
|
let log = Log::new(&config.logs_directory);
|
||||||
|
|
||||||
event!(Level::INFO, "Configuration: {:?}", config);
|
event!(Level::INFO, "Configuration: {:?}", config);
|
||||||
|
|
||||||
|
|
@ -133,6 +141,7 @@ async fn main() {
|
||||||
let state = AppState {
|
let state = AppState {
|
||||||
config,
|
config,
|
||||||
db_connection,
|
db_connection,
|
||||||
|
log,
|
||||||
};
|
};
|
||||||
|
|
||||||
let ron_api_routes = Router::new()
|
let ron_api_routes = Router::new()
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use axum::{
|
use axum::{
|
||||||
body, debug_handler,
|
body, debug_handler,
|
||||||
extract::{Extension, Request, State},
|
extract::{Extension, Query, Request, State},
|
||||||
http::{StatusCode, header},
|
http::{StatusCode, header},
|
||||||
middleware::Next,
|
middleware::Next,
|
||||||
response::{Html, IntoResponse, Response},
|
response::{Html, IntoResponse, Response},
|
||||||
};
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{Context, Result, consts, data::db, html_templates::*, ron_utils};
|
use crate::{AppState, Context, Result, consts, data::db, html_templates::*, log::Log, ron_utils};
|
||||||
|
|
||||||
pub mod fragments;
|
pub mod fragments;
|
||||||
pub mod recipe;
|
pub mod recipe;
|
||||||
|
|
@ -62,10 +63,18 @@ pub async fn home_page(
|
||||||
|
|
||||||
///// DEV_PANEL /////
|
///// DEV_PANEL /////
|
||||||
|
|
||||||
#[debug_handler]
|
#[derive(Deserialize)]
|
||||||
|
pub struct LogFile {
|
||||||
|
#[serde(default)]
|
||||||
|
pub log_file: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[debug_handler(state = AppState)]
|
||||||
pub async fn dev_panel(
|
pub async fn dev_panel(
|
||||||
State(connection): State<db::Connection>,
|
State(connection): State<db::Connection>,
|
||||||
|
State(log): State<Log>,
|
||||||
Extension(context): Extension<Context>,
|
Extension(context): Extension<Context>,
|
||||||
|
log_file: Query<LogFile>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
if context.user.is_some() && context.user.as_ref().unwrap().is_admin {
|
if context.user.is_some() && context.user.as_ref().unwrap().is_admin {
|
||||||
Ok(Html(
|
Ok(Html(
|
||||||
|
|
@ -73,6 +82,8 @@ pub async fn dev_panel(
|
||||||
recipes: Recipes::new(connection, &context.user, context.tr.current_lang_code())
|
recipes: Recipes::new(connection, &context.user, context.tr.current_lang_code())
|
||||||
.await?,
|
.await?,
|
||||||
context,
|
context,
|
||||||
|
log,
|
||||||
|
current_log_file: log_file.log_file.clone(),
|
||||||
}
|
}
|
||||||
.render()?,
|
.render()?,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,36 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="content" id="dev_panel">
|
<div class="content" id="dev-panel">
|
||||||
<input type="button" class="button" id="test-toast" value="Test toast">
|
<input type="button" class="button" id="test-toast" value="Test toast">
|
||||||
<input type="button" class="button" id="test-modal-dialog" value="Test modal">
|
<input type="button" class="button" id="test-modal-dialog" value="Test modal">
|
||||||
|
|
||||||
|
<div type="log">
|
||||||
|
<ul class="log-files">
|
||||||
|
{% for f in log.file_names().unwrap() %}
|
||||||
|
<li class="log-file"><a href="/dev_panel?log_file={{ f }}">{{ f }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<div class="current-log-file">
|
||||||
|
{% match log.read_content(current_log_file) %}
|
||||||
|
{% when Ok(lines) %}
|
||||||
|
{% for l in lines %}
|
||||||
|
<div class="
|
||||||
|
{% if loop.index0 % 2 == 0 %}
|
||||||
|
log-line-even
|
||||||
|
{% else %}
|
||||||
|
log-line-odd
|
||||||
|
{% endif %}
|
||||||
|
" >{{ l }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% when Err(err) %}
|
||||||
|
Error reading log: {{ err }}
|
||||||
|
{% endmatch %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="hidden-templates">
|
<div id="hidden-templates">
|
||||||
|
|
||||||
<div class="modal-test-message">
|
<div class="modal-test-message">
|
||||||
This is a message.
|
This is a message.
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue