use crate::{AnalysisResult, FindingSet, LogEntry, PgLogstatsError, Result, TimingAnalysis};
use std::fmt::Write;
pub fn bold(s: &str, color: Option<&str>, enable_color: bool) -> String {
if !enable_color {
return s.to_string();
}
let code = match color.unwrap_or("white") {
"red" => "\x1b[31;1m",
"green" => "\x1b[32;1m",
"yellow" => "\x1b[33;1m",
"blue" => "\x1b[34;1m",
"magenta" => "\x1b[35;1m",
"cyan" => "\x1b[36;1m",
_ => "\x1b[37;1m",
};
format!("{}{}\x1b[0m", code, s)
}
pub struct TextFormatter {
enable_color: bool,
}
impl TextFormatter {
pub fn new() -> Self {
Self {
enable_color: false,
}
}
pub fn with_color(mut self, enable: bool) -> Self {
self.enable_color = enable;
self
}
pub fn is_color_enabled(&self) -> bool {
self.enable_color
}
pub fn format_query_analysis(&self, analysis: &AnalysisResult) -> Result<String> {
let mut output = String::new();
writeln!(
output,
"{}",
bold("Query Analysis Report", Some("cyan"), self.enable_color)
)
.map_err(|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
})?;
writeln!(
output,
"{}",
bold("===================", Some("cyan"), self.enable_color)
)
.map_err(|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
})?;
writeln!(output, "Total Queries: {}", analysis.total_queries).map_err(|e| {
PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
}
})?;
writeln!(output, "Total Duration: {:.2} ms", analysis.total_duration).map_err(|e| {
PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
}
})?;
writeln!(
output,
"Average Duration: {:.2} ms",
analysis.average_duration
)
.map_err(|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
})?;
writeln!(output, "P95 Duration: {:.2} ms", analysis.p95_duration).map_err(|e| {
PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
}
})?;
writeln!(output, "P99 Duration: {:.2} ms", analysis.p99_duration).map_err(|e| {
PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
}
})?;
writeln!(output, "Error Count: {}", analysis.error_count).map_err(|e| {
PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
}
})?;
writeln!(output, "Connection Count: {}", analysis.connection_count).map_err(|e| {
PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
}
})?;
if !analysis.query_types.is_empty() {
writeln!(
output,
"\n{}",
bold("Query Types:", Some("yellow"), self.enable_color)
)
.map_err(|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
})?;
for (query_type, count) in &analysis.query_types {
writeln!(output, " {:>8}: {}", query_type, count).map_err(|e| {
PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
}
})?;
}
}
if !analysis.slowest_queries.is_empty() {
writeln!(
output,
"\n{}",
bold("Slowest Queries:", Some("red"), self.enable_color)
)
.map_err(|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
})?;
writeln!(output, " {:>4} {:>12} Query", "#", "Duration (ms)").map_err(|e| {
PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
}
})?;
for (i, (query, duration)) in analysis.slowest_queries.iter().enumerate() {
writeln!(output, " {:>4} {:>12.2} {}", i + 1, duration, query).map_err(|e| {
PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
}
})?;
}
}
if !analysis.most_frequent_queries.is_empty() {
writeln!(
output,
"\n{}",
bold("Most Frequent Queries:", Some("green"), self.enable_color)
)
.map_err(|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
})?;
writeln!(output, " {:>4} {:>8} Query", "#", "Count").map_err(|e| {
PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
}
})?;
for (i, (query, count)) in analysis.most_frequent_queries.iter().enumerate() {
writeln!(output, " {:>4} {:>8} {}", i + 1, count, query).map_err(|e| {
PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
}
})?;
}
}
Ok(output)
}
pub fn format_timing_analysis(&self, analysis: &TimingAnalysis) -> Result<String> {
let mut output = String::new();
writeln!(
output,
"{}",
bold("Timing Analysis Report", Some("cyan"), self.enable_color)
)
.map_err(|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
})?;
writeln!(
output,
"{}",
bold("====================", Some("cyan"), self.enable_color)
)
.map_err(|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
})?;
writeln!(
output,
"Average Response Time: {}ms",
analysis.average_response_time.num_milliseconds()
)
.map_err(|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
})?;
writeln!(
output,
"95th Percentile: {}ms",
analysis.p95_response_time.num_milliseconds()
)
.map_err(|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
})?;
writeln!(
output,
"99th Percentile: {}ms",
analysis.p99_response_time.num_milliseconds()
)
.map_err(|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
})?;
Ok(output)
}
pub fn format_findings(&self, findings: &FindingSet) -> Result<String> {
let mut output = String::new();
writeln!(
output,
"{}",
bold("Findings", Some("cyan"), self.enable_color)
)
.map_err(|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
})?;
writeln!(output, "Schema Version: {}", findings.schema_version).map_err(|e| {
PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
}
})?;
for finding in &findings.findings {
writeln!(
output,
"\n#{} [{}] {}",
finding.rank, finding.finding_id, finding.title
)
.map_err(|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
})?;
writeln!(output, "Reason: {}", finding.reason).map_err(|e| {
PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
}
})?;
writeln!(
output,
"Score: {:.3} Confidence: {:?}",
finding.score, finding.confidence
)
.map_err(|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
})?;
if let Some(query_family) = &finding.query_family {
writeln!(output, "Query Family: {}", query_family.query_family_id).map_err(
|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
},
)?;
writeln!(output, "SQL: {}", query_family.normalized_sql).map_err(|e| {
PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
}
})?;
}
}
Ok(output)
}
pub fn format_log_entries(&self, entries: &[LogEntry]) -> Result<String> {
let mut output = String::new();
writeln!(
output,
"{}",
bold(
&format!("Log Entries ({} total)", entries.len()),
Some("magenta"),
self.enable_color
)
)
.map_err(|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
})?;
writeln!(
output,
"{}",
bold("================", Some("magenta"), self.enable_color)
)
.map_err(|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
})?;
for (i, entry) in entries.iter().enumerate() {
writeln!(
output,
"[{}] {} {}: {}",
i + 1,
entry.timestamp.format("%Y-%m-%d %H:%M:%S"),
entry.message_type,
entry.message
)
.map_err(|e| PgLogstatsError::Unexpected {
message: e.to_string(),
context: Some("text formatting".to_string()),
})?;
}
Ok(output)
}
}
impl Default for TextFormatter {
fn default() -> Self {
Self::new()
}
}