add tracing, env config, dotenvy
bootstrap: tracing-subscriber with RUST_LOG env filter, ServerConfig from env vars (KFRAME_DATABASE_URL, KFRAME_TCP_ADDR, etc.), dotenvy for .env file loading. Replaced all println with tracing macros. tcp-server: replaced println with tracing::info/warn. Added .env.example and .gitignore for db files.
This commit is contained in:
@@ -12,3 +12,6 @@ http-api.workspace = true
|
||||
http-json.workspace = true
|
||||
tokio.workspace = true
|
||||
anyhow.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
dotenvy.workspace = true
|
||||
|
||||
25
crates/bootstrap/src/config.rs
Normal file
25
crates/bootstrap/src/config.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use std::env;
|
||||
|
||||
pub struct ServerConfig {
|
||||
pub database_url: String,
|
||||
pub tcp_addr: String,
|
||||
pub http_addr: String,
|
||||
pub poll_interval_secs: u64,
|
||||
}
|
||||
|
||||
impl ServerConfig {
|
||||
pub fn from_env() -> Self {
|
||||
Self {
|
||||
database_url: env::var("KFRAME_DATABASE_URL")
|
||||
.unwrap_or_else(|_| "sqlite:kframe.db?mode=rwc".into()),
|
||||
tcp_addr: env::var("KFRAME_TCP_ADDR")
|
||||
.unwrap_or_else(|_| "0.0.0.0:2699".into()),
|
||||
http_addr: env::var("KFRAME_HTTP_ADDR")
|
||||
.unwrap_or_else(|_| "0.0.0.0:3000".into()),
|
||||
poll_interval_secs: env::var("KFRAME_POLL_INTERVAL_SECS")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(5),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
mod config;
|
||||
mod polling;
|
||||
|
||||
use std::sync::Arc;
|
||||
@@ -7,34 +8,50 @@ use config_sqlite::SqliteConfigStore;
|
||||
use tcp_server::{TcpBroadcaster, TcpEventBus, run_tcp_server};
|
||||
use http_api::AppState;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
const DB_PATH: &str = "sqlite:kframe.db?mode=rwc";
|
||||
const TCP_ADDR: &str = "0.0.0.0:2699";
|
||||
const HTTP_ADDR: &str = "0.0.0.0:3000";
|
||||
use tracing::{info, error};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let config_store = Arc::new(SqliteConfigStore::new(DB_PATH).await?);
|
||||
dotenvy::dotenv().ok();
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| "info,sqlx=warn".into()),
|
||||
)
|
||||
.init();
|
||||
|
||||
let cfg = config::ServerConfig::from_env();
|
||||
|
||||
info!(db = %cfg.database_url, "connecting to database");
|
||||
let config_store = Arc::new(SqliteConfigStore::new(&cfg.database_url).await?);
|
||||
|
||||
let event_bus = Arc::new(TcpEventBus::new(64));
|
||||
let broadcaster = Arc::new(TcpBroadcaster::new(64));
|
||||
let projection = Arc::new(Mutex::new(DataProjection::new()));
|
||||
|
||||
let tcp_addr = cfg.tcp_addr.clone();
|
||||
let tcp_bc = broadcaster.clone();
|
||||
tokio::spawn(async move {
|
||||
run_tcp_server(TCP_ADDR, tcp_bc).await.unwrap();
|
||||
if let Err(e) = run_tcp_server(&tcp_addr, tcp_bc).await {
|
||||
error!(error = %e, "tcp server failed");
|
||||
}
|
||||
});
|
||||
println!("TCP server on {TCP_ADDR}");
|
||||
info!(addr = %cfg.tcp_addr, "TCP server started");
|
||||
|
||||
let http_addr = cfg.http_addr.clone();
|
||||
let http_state = AppState {
|
||||
config: config_store.clone(),
|
||||
events: event_bus.clone(),
|
||||
};
|
||||
tokio::spawn(async move {
|
||||
http_api::serve(HTTP_ADDR, http_state).await.unwrap();
|
||||
if let Err(e) = http_api::serve(&http_addr, http_state).await {
|
||||
error!(error = %e, "HTTP API failed");
|
||||
}
|
||||
});
|
||||
println!("HTTP API on {HTTP_ADDR}");
|
||||
info!(addr = %cfg.http_addr, "HTTP API started");
|
||||
|
||||
println!("K-Frame server running");
|
||||
info!("K-Frame server running");
|
||||
|
||||
polling::run(config_store, broadcaster, projection).await
|
||||
polling::run(config_store, broadcaster, projection, cfg.poll_interval_secs).await
|
||||
}
|
||||
|
||||
@@ -10,18 +10,21 @@ use http_json::HttpJsonAdapter;
|
||||
use tcp_server::TcpBroadcaster;
|
||||
use config_sqlite::SqliteConfigStore;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
const POLL_CHECK_INTERVAL: Duration = Duration::from_secs(5);
|
||||
use tracing::{info, warn, debug};
|
||||
|
||||
pub async fn run(
|
||||
config: Arc<SqliteConfigStore>,
|
||||
broadcaster: Arc<TcpBroadcaster>,
|
||||
projection: Arc<Mutex<DataProjection>>,
|
||||
poll_interval_secs: u64,
|
||||
) -> Result<()> {
|
||||
let http_adapter = HttpJsonAdapter::new();
|
||||
let interval = Duration::from_secs(poll_interval_secs);
|
||||
|
||||
info!(interval_secs = poll_interval_secs, "polling loop started");
|
||||
|
||||
loop {
|
||||
tokio::time::sleep(POLL_CHECK_INTERVAL).await;
|
||||
tokio::time::sleep(interval).await;
|
||||
|
||||
let sources = config.list_data_sources().await
|
||||
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||
@@ -31,6 +34,7 @@ pub async fn run(
|
||||
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||
|
||||
if sources.is_empty() || widgets.is_empty() {
|
||||
debug!("no sources or widgets configured, skipping poll");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -40,7 +44,7 @@ pub async fn run(
|
||||
let result = match poll_source(&http_adapter, source).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("poll error for '{}': {e}", source.name);
|
||||
warn!(source = %source.name, error = %e, "poll failed");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
@@ -55,7 +59,7 @@ pub async fn run(
|
||||
broadcaster.push_screen_update(l, &all_changed).await
|
||||
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||
}
|
||||
println!("pushed {} widget updates", all_changed.len());
|
||||
info!(count = all_changed.len(), "pushed widget updates");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user