From 068b5f472ccbc2e805175e9662027b1148837b78 Mon Sep 17 00:00:00 2001 From: Milo <50248166+Milo123459@users.noreply.github.com> Date: Sun, 5 May 2024 11:32:47 +0100 Subject: [PATCH] Add attribute logging (#455) --- .cargo/{config => config.toml} | 0 config.toml | 1 + src/commands/logs.rs | 57 ++++++++++++++---- src/commands/up.rs | 9 +-- .../strings/DeploymentLogs.graphql | 22 +++---- src/util/logs.rs | 58 +++++++++++++++++++ src/util/mod.rs | 1 + 7 files changed, 122 insertions(+), 26 deletions(-) rename .cargo/{config => config.toml} (100%) create mode 100644 config.toml create mode 100644 src/util/logs.rs diff --git a/.cargo/config b/.cargo/config.toml similarity index 100% rename from .cargo/config rename to .cargo/config.toml diff --git a/config.toml b/config.toml new file mode 100644 index 000000000..8b3b04ea6 --- /dev/null +++ b/config.toml @@ -0,0 +1 @@ +Commit message: `config - and fix other things` (default-config) diff --git a/src/commands/logs.rs b/src/commands/logs.rs index 66bc8411f..c1049372f 100644 --- a/src/commands/logs.rs +++ b/src/commands/logs.rs @@ -1,9 +1,15 @@ -use crate::controllers::{ - deployment::{stream_build_logs, stream_deploy_logs}, - environment::get_matched_environment, - project::get_project, +use std::collections::HashMap; + +use crate::{ + controllers::{ + deployment::{stream_build_logs, stream_deploy_logs}, + environment::get_matched_environment, + project::get_project, + }, + util::logs::format_attr_log, }; use anyhow::bail; +use serde_json::Value; use super::{ queries::deployments::{DeploymentListInput, DeploymentStatus}, @@ -108,13 +114,42 @@ pub async fn command(args: Args, json: bool) -> Result<()> { }) .await?; } else { - stream_deploy_logs(deployment.id.clone(), |log| { - if json { - println!("{}", serde_json::to_string(&log).unwrap()); - } else { - println!("{}", log.message); - } - }) + stream_deploy_logs( + deployment.id.clone(), + |log: subscriptions::deployment_logs::LogFields| { + if json { + let mut map: HashMap = HashMap::new(); + + // Insert fixed attributes + map.insert( + "message".to_string(), + serde_json::to_value(log.message.clone()).unwrap(), + ); + map.insert( + "timestamp".to_string(), + serde_json::to_value(log.timestamp.clone()).unwrap(), + ); + + // Insert dynamic attributes + for attribute in log.attributes { + // Trim surrounding quotes if present + let value = match attribute.value.trim_matches('"').parse::() { + Ok(value) => value, + Err(_) => { + serde_json::to_value(attribute.value.trim_matches('"')).unwrap() + } + }; + map.insert(attribute.key, value); + } + + // Convert HashMap to JSON string + let json_string = serde_json::to_string(&map).unwrap(); + println!("{}", json_string); + } else { + format_attr_log(log); + } + }, + ) .await?; } diff --git a/src/commands/up.rs b/src/commands/up.rs index 94f4cb3e2..47f07dde9 100644 --- a/src/commands/up.rs +++ b/src/commands/up.rs @@ -24,7 +24,10 @@ use crate::{ project::get_project, }, errors::RailwayError, - util::prompt::{prompt_select, PromptService}, + util::{ + logs::format_attr_log, + prompt::{prompt_select, PromptService}, + }, }; use super::*; @@ -325,9 +328,7 @@ pub async fn command(args: Args, _json: bool) -> Result<()> { // Stream deploy logs only if ci flag is not set if !args.ci { tasks.push(tokio::task::spawn(async move { - if let Err(e) = - stream_deploy_logs(deploy_deployment_id, |log| println!("{}", log.message)).await - { + if let Err(e) = stream_deploy_logs(deploy_deployment_id, format_attr_log).await { eprintln!("Failed to stream deploy logs: {}", e); } })); diff --git a/src/gql/subscriptions/strings/DeploymentLogs.graphql b/src/gql/subscriptions/strings/DeploymentLogs.graphql index 00b1ee9c7..eefd1e150 100644 --- a/src/gql/subscriptions/strings/DeploymentLogs.graphql +++ b/src/gql/subscriptions/strings/DeploymentLogs.graphql @@ -1,14 +1,14 @@ -subscription DeploymentLogs( - $deploymentId: String! - $filter: String - $limit: Int -) { - deploymentLogs(deploymentId: $deploymentId, filter: $filter, limit: $limit) { - ...LogFields - } +subscription DeploymentLogs($deploymentId: String!, $filter: String, $limit: Int) { + deploymentLogs(deploymentId: $deploymentId, filter: $filter, limit: $limit) { + ...LogFields + } } fragment LogFields on Log { - timestamp - message -} + timestamp + message + attributes { + key + value + } +} \ No newline at end of file diff --git a/src/util/logs.rs b/src/util/logs.rs new file mode 100644 index 000000000..860aede6f --- /dev/null +++ b/src/util/logs.rs @@ -0,0 +1,58 @@ +use crate::subscriptions; +use colored::Colorize; + +pub fn format_attr_log(log: subscriptions::deployment_logs::LogFields) { + // we love inconsistencies! + if log.attributes.is_empty() + || (log.attributes.len() == 1 + && log + .attributes + .first() + .is_some_and(|attr| attr.key == "level")) + { + println!("{}", log.message); + return; + } + + let mut level: Option = None; + let message = log.message; + let mut others = Vec::new(); + // get attributes using a match + for attr in &log.attributes { + match attr.key.to_lowercase().as_str() { + "level" | "lvl" | "severity" => level = Some(attr.value.clone()), + _ => others.push(format!( + "{}{}{}", + attr.key.clone().magenta(), + "=", + attr.value + .clone() + .normal() + .replace('"', "\"".dimmed().to_string().as_str()) + )), + } + } + // get the level and colour it + let level = level + .map(|level| { + // make it uppercase so we dont have to make another variable + // for some reason, .uppercase() removes formatting + + match level.replace('"', "").to_lowercase().as_str() { + "info" => "[INFO]".blue(), + "error" | "err" => "[ERRO]".red(), + "warn" => "[WARN]".yellow(), + "debug" => "[DBUG]".dimmed(), + _ => format!("[{}]", level).normal(), + } + .bold() + }) + .unwrap(); + println!( + "{} {} {} {}", + log.timestamp.replace('"', "").normal(), + level, + message, + others.join(" ") + ); +} diff --git a/src/util/mod.rs b/src/util/mod.rs index 8bf84783c..4c1a3567f 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1 +1,2 @@ +pub mod logs; pub mod prompt;