refactor: standardize structured logging across Rust and TypeScript

Convert string interpolation to structured fields in tracing/LogTape calls. Add target prefixes (rust::, bun:) to differentiate processes in combined logs.
This commit is contained in:
2026-01-13 15:33:08 -06:00
parent fe23967c5e
commit 6d8766d3a6
5 changed files with 35 additions and 21 deletions
+7 -7
View File
@@ -72,8 +72,8 @@ impl SessionManager {
} }
tracing::info!( tracing::info!(
"Loaded {} active sessions from database", session_count = self.sessions.len(),
self.sessions.len() "Loaded active sessions from database"
); );
Ok(()) Ok(())
@@ -111,7 +111,7 @@ impl SessionManager {
self.sessions.insert(id, session.clone()); self.sessions.insert(id, session.clone());
tracing::debug!("Created session {} for user {}", id, user_id); tracing::debug!(session_id = %id, user_id, "Created session");
Ok(session) Ok(session)
} }
@@ -139,7 +139,7 @@ impl SessionManager {
.execute(&self.pool) .execute(&self.pool)
.await?; .await?;
tracing::debug!("Deleted session {}", session_id); tracing::debug!(session_id = %session_id, "Deleted session");
Ok(()) Ok(())
} }
@@ -157,7 +157,7 @@ impl SessionManager {
self.sessions.retain(|_, session| session.expires_at >= now); self.sessions.retain(|_, session| session.expires_at >= now);
if expired_count > 0 { if expired_count > 0 {
tracing::info!("Cleaned up {} expired sessions", expired_count); tracing::info!(expired_count, "Cleaned up expired sessions");
} }
Ok(expired_count) Ok(expired_count)
@@ -234,9 +234,9 @@ pub async fn ensure_admin_user(pool: &PgPool) -> Result<(), Box<dyn std::error::
if get_admin_user(pool, &username).await?.is_none() { if get_admin_user(pool, &username).await?.is_none() {
create_admin_user(pool, &username, &password).await?; create_admin_user(pool, &username, &password).await?;
tracing::info!("Created admin user: {}", username); tracing::info!(username, "Created admin user");
} else { } else {
tracing::debug!("Admin user '{}' already exists", username); tracing::debug!(username, "Admin user already exists");
} }
Ok(()) Ok(())
+15 -3
View File
@@ -13,6 +13,17 @@ use tracing_subscriber::registry::LookupSpan;
const TIMESTAMP_FORMAT: &[FormatItem<'static>] = const TIMESTAMP_FORMAT: &[FormatItem<'static>] =
format_description!("[hour]:[minute]:[second].[subsecond digits:3]"); format_description!("[hour]:[minute]:[second].[subsecond digits:3]");
/// Transform tracing target from `api::*` to `rust::*` for combined log differentiation
fn transform_target(target: &str) -> String {
if target == "api" {
"rust".to_string()
} else if let Some(rest) = target.strip_prefix("api::") {
format!("rust::{rest}")
} else {
target.to_string()
}
}
pub struct CustomPrettyFormatter; pub struct CustomPrettyFormatter;
impl<S, N> FormatEvent<S, N> for CustomPrettyFormatter impl<S, N> FormatEvent<S, N> for CustomPrettyFormatter
@@ -63,10 +74,11 @@ where
} }
} }
let target = transform_target(meta.target());
if writer.has_ansi_escapes() { if writer.has_ansi_escapes() {
write!(writer, "{}: ", Color::DarkGray.paint(meta.target()))?; write!(writer, "{}: ", Color::DarkGray.paint(&target))?;
} else { } else {
write!(writer, "{}: ", meta.target())?; write!(writer, "{}: ", target)?;
} }
ctx.format_fields(writer.by_ref(), event)?; ctx.format_fields(writer.by_ref(), event)?;
@@ -189,7 +201,7 @@ where
.unwrap_or_else(|_| String::from("1970-01-01T00:00:00Z")), .unwrap_or_else(|_| String::from("1970-01-01T00:00:00Z")),
message: message.unwrap_or_default(), message: message.unwrap_or_default(),
level: meta.level().to_string().to_lowercase(), level: meta.level().to_string().to_lowercase(),
target: meta.target().to_string(), target: transform_target(meta.target()),
fields, fields,
}; };
+3 -5
View File
@@ -88,7 +88,7 @@ async fn main() {
.expect("Failed to connect to database"); .expect("Failed to connect to database");
// Run migrations on startup // Run migrations on startup
tracing::info!("Running database migrations..."); tracing::info!("Running database migrations");
sqlx::migrate!().run(&pool).await.unwrap_or_else(|e| { sqlx::migrate!().run(&pool).await.unwrap_or_else(|e| {
tracing::error!(error = %e, "Migration failed"); tracing::error!(error = %e, "Migration failed");
std::process::exit(1); std::process::exit(1);
@@ -146,10 +146,8 @@ async fn main() {
tracing::info!( tracing::info!(
enabled = tarpit_state.config.enabled, enabled = tarpit_state.config.enabled,
delay_range_ms = format!( delay_min_ms = tarpit_state.config.delay_min_ms,
"{}-{}", delay_max_ms = tarpit_state.config.delay_max_ms,
tarpit_state.config.delay_min_ms, tarpit_state.config.delay_max_ms
),
max_global = tarpit_state.config.max_global_connections, max_global = tarpit_state.config.max_global_connections,
max_per_ip = tarpit_state.config.max_connections_per_ip, max_per_ip = tarpit_state.config.max_connections_per_ip,
"Tarpit initialized" "Tarpit initialized"
+2 -1
View File
@@ -10,11 +10,12 @@ interface RailwayLogEntry {
} }
function railwayFormatter(record: LogRecord): string { function railwayFormatter(record: LogRecord): string {
const categoryTarget = record.category.join(":");
const entry: RailwayLogEntry = { const entry: RailwayLogEntry = {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
level: record.level.toLowerCase(), level: record.level.toLowerCase(),
message: record.message.join(" "), message: record.message.join(" "),
target: record.category.join(":"), target: categoryTarget ? `bun:${categoryTarget}` : "bun",
}; };
if (record.properties && Object.keys(record.properties).length > 0) { if (record.properties && Object.keys(record.properties).length > 0) {
+8 -5
View File
@@ -54,13 +54,15 @@ async function loadCollectionFromDisk(
// Cache the collection // Cache the collection
collectionCache.set(collection, iconSet); collectionCache.set(collection, iconSet);
logger.debug(`Loaded icon collection: ${collection}`, { logger.debug("Loaded icon collection", {
collection,
total: iconSet.info?.total || Object.keys(iconSet.icons).length, total: iconSet.info?.total || Object.keys(iconSet.icons).length,
}); });
return iconSet; return iconSet;
} catch (error) { } catch (error) {
logger.warn(`Failed to load icon collection: ${collection}`, { logger.warn("Failed to load icon collection", {
collection,
error: error instanceof Error ? error.message : String(error), error: error instanceof Error ? error.message : String(error),
}); });
return null; return null;
@@ -239,7 +241,8 @@ export async function renderIconsBatch(
const svg = renderIconData(iconData, options); const svg = renderIconData(iconData, options);
results.set(identifier, svg); results.set(identifier, svg);
} catch (error) { } catch (error) {
logger.warn(`Failed to render icon: ${identifier}`, { logger.warn("Failed to render icon", {
identifier,
error: error instanceof Error ? error.message : String(error), error: error instanceof Error ? error.message : String(error),
}); });
missingIcons.push(identifier); missingIcons.push(identifier);
@@ -277,7 +280,7 @@ export async function getIconForApi(identifier: string): Promise<{
} | null> { } | null> {
const parsed = parseIdentifier(identifier); const parsed = parseIdentifier(identifier);
if (!parsed) { if (!parsed) {
logger.warn(`Invalid icon identifier: ${identifier}`); logger.warn("Invalid icon identifier", { identifier });
return null; return null;
} }
@@ -290,7 +293,7 @@ export async function getIconForApi(identifier: string): Promise<{
const iconData = getIconData(iconSet, name); const iconData = getIconData(iconSet, name);
if (!iconData) { if (!iconData) {
logger.warn(`Icon not found: ${identifier}`); logger.warn("Icon not found", { identifier });
return null; return null;
} }