Skip to content

Commit a11cf52

Browse files
authored
feat: add optional colored log output (#52)
1 parent 0857121 commit a11cf52

File tree

8 files changed

+98
-43
lines changed

8 files changed

+98
-43
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ license agreements:
210210
Dual-licensed under [Apache 2.0][Apache2] or [MIT].
211211
- [chrono](https://crates.io/crates/chrono):
212212
Dual-licensed under [Apache 2.0][Apache2] or [MIT].
213+
- [colored](https://crates.io/crates/colored): Licensed under [MPL-2.0]
213214

214215
The python binding uses
215216

@@ -223,3 +224,4 @@ The node binding uses
223224

224225
[MIT]: https://choosealicense.com/licenses/mit
225226
[Apache2]: https://choosealicense.com/licenses/apache-2.0/
227+
[MPL-2.0]: https://choosealicense.com/licenses/mpl-2.0

cpp-linter/Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ license.workspace = true
1717
anyhow = "1.0.89"
1818
chrono = "0.4.38"
1919
clap = "4.5.17"
20+
colored = "2.1.0"
2021
fast-glob = "0.4.0"
2122
futures = "0.3.30"
2223
git2 = "0.19.0"
2324
lenient_semver = "0.4.2"
24-
log = "0.4.22"
25+
log = { version = "0.4.22", features = ["std"] }
2526
openssl = { version = "0.10", features = ["vendored"], optional = true }
2627
openssl-probe = { version = "0.1", optional = true }
2728
regex = "1.10.6"

cpp-linter/src/clang_tools/mod.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ use which::{which, which_in};
2121
use super::common_fs::FileObj;
2222
use crate::{
2323
cli::ClangParams,
24-
logger::{end_log_group, start_log_group},
25-
rest_api::{COMMENT_MARKER, USER_OUTREACH},
24+
rest_api::{RestApiClient, COMMENT_MARKER, USER_OUTREACH},
2625
};
2726
pub mod clang_format;
2827
use clang_format::run_clang_format;
@@ -144,6 +143,7 @@ pub async fn capture_clang_tools_output(
144143
files: &mut Vec<Arc<Mutex<FileObj>>>,
145144
version: &str,
146145
clang_params: &mut ClangParams,
146+
rest_api_client: &impl RestApiClient,
147147
) -> Result<()> {
148148
// find the executable paths for clang-tidy and/or clang-format and show version
149149
// info as debugging output.
@@ -192,11 +192,11 @@ pub async fn capture_clang_tools_output(
192192
while let Some(output) = executors.join_next().await {
193193
if let Ok(out) = output? {
194194
let (file_name, logs) = out;
195-
start_log_group(format!("Analyzing {}", file_name.to_string_lossy()));
195+
rest_api_client.start_log_group(format!("Analyzing {}", file_name.to_string_lossy()));
196196
for (level, msg) in logs {
197197
log::log!(level, "{}", msg);
198198
}
199-
end_log_group();
199+
rest_api_client.end_log_group();
200200
}
201201
}
202202
Ok(())

cpp-linter/src/logger.rs

+50-31
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,77 @@
11
//! A module to initialize and customize the logger object used in (most) stdout.
22
3-
// non-std crates
4-
use log::{Level, LevelFilter, Metadata, Record, SetLoggerError};
3+
use std::env;
54

5+
use anyhow::{Error, Result};
6+
use colored::{control::set_override, Colorize};
7+
use log::{Level, LevelFilter, Metadata, Record};
8+
9+
#[derive(Default)]
610
struct SimpleLogger;
711

12+
impl SimpleLogger {
13+
fn level_color(level: &Level) -> String {
14+
let name = format!("{:>5}", level.as_str().to_uppercase());
15+
match level {
16+
Level::Error => name.red().bold().to_string(),
17+
Level::Warn => name.yellow().bold().to_string(),
18+
Level::Info => name.green().bold().to_string(),
19+
Level::Debug => name.blue().bold().to_string(),
20+
Level::Trace => name.magenta().bold().to_string(),
21+
}
22+
}
23+
}
24+
825
impl log::Log for SimpleLogger {
926
fn enabled(&self, metadata: &Metadata) -> bool {
10-
metadata.level() <= Level::Debug
27+
metadata.level() <= log::max_level()
1128
}
1229

1330
fn log(&self, record: &Record) {
14-
if self.enabled(record.metadata()) {
15-
println!("{}: {}", record.level(), record.args());
31+
if record.target() == "CI_LOG_GROUPING" {
32+
// this log is meant to manipulate a CI workflow's log grouping
33+
println!("{}", record.args());
34+
} else if self.enabled(record.metadata()) {
35+
println!(
36+
"[{}]: {}",
37+
Self::level_color(&record.level()),
38+
record.args()
39+
);
1640
}
1741
}
1842

1943
fn flush(&self) {}
2044
}
2145

22-
/// A private constant to manage the application's logger object.
23-
static LOGGER: SimpleLogger = SimpleLogger;
24-
2546
/// A function to initialize the private `LOGGER`.
2647
///
2748
/// The logging level defaults to [`LevelFilter::Info`].
2849
/// Returns a [`SetLoggerError`] if the `LOGGER` is already initialized.
29-
pub fn init() -> Result<(), SetLoggerError> {
30-
log::set_logger(&LOGGER).map(|()| log::set_max_level(LevelFilter::Info))
31-
}
32-
33-
/// This prints a line to indicate the beginning of a related group of log statements.
34-
///
35-
/// This function may or may not get moved to [crate::rest_api::RestApiClient] trait
36-
/// if/when platforms other than GitHub are supported.
37-
pub fn start_log_group(name: String) {
38-
println!("::group::{}", name);
39-
}
40-
41-
/// This prints a line to indicate the ending of a related group of log statements.
42-
///
43-
/// This function may or may not get moved to [crate::rest_api::RestApiClient] trait
44-
/// if/when platforms other than GitHub are supported.
45-
pub fn end_log_group() {
46-
println!("::endgroup::");
50+
pub fn init() -> Result<()> {
51+
let logger: SimpleLogger = SimpleLogger;
52+
if matches!(
53+
env::var("CPP_LINTER_COLOR").as_deref(),
54+
Ok("on" | "1" | "true")
55+
) {
56+
set_override(true);
57+
}
58+
log::set_boxed_logger(Box::new(logger))
59+
.map(|()| log::set_max_level(LevelFilter::Info))
60+
.map_err(Error::from)
4761
}
4862

4963
#[cfg(test)]
50-
mod tests {
51-
use super::{end_log_group, start_log_group};
64+
mod test {
65+
use std::env;
66+
67+
use super::{init, SimpleLogger};
5268

5369
#[test]
54-
fn issue_log_grouping_stdout() {
55-
start_log_group(String::from("a dumb test"));
56-
end_log_group();
70+
fn trace_log() {
71+
env::set_var("CPP_LINTER_COLOR", "true");
72+
init().unwrap_or(());
73+
assert!(SimpleLogger::level_color(&log::Level::Trace).contains("TRACE"));
74+
log::set_max_level(log::LevelFilter::Trace);
75+
log::trace!("A dummy log statement for code coverage");
5776
}
5877
}

cpp-linter/src/rest_api/github/mod.rs

+10
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,16 @@ impl RestApiClient for GithubApiClient {
9595
checks_failed
9696
}
9797

98+
/// This prints a line to indicate the beginning of a related group of log statements.
99+
fn start_log_group(&self, name: String) {
100+
log::info!(target: "CI_LOG_GROUPING", "::group::{}", name);
101+
}
102+
103+
/// This prints a line to indicate the ending of a related group of log statements.
104+
fn end_log_group(&self) {
105+
log::info!(target: "CI_LOG_GROUPING", "::endgroup::");
106+
}
107+
98108
fn make_headers() -> Result<HeaderMap<HeaderValue>> {
99109
let mut headers = HeaderMap::new();
100110
headers.insert(

cpp-linter/src/rest_api/mod.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ pub trait RestApiClient {
4949
tidy_checks_failed: Option<u64>,
5050
) -> u64;
5151

52+
/// This prints a line to indicate the beginning of a related group of log statements.
53+
fn start_log_group(&self, name: String);
54+
55+
/// This prints a line to indicate the ending of a related group of log statements.
56+
fn end_log_group(&self);
57+
5258
/// A convenience method to create the headers attached to all REST API calls.
5359
///
5460
/// If an authentication token is provided (via environment variable),
@@ -453,12 +459,21 @@ mod test {
453459
) -> Result<u64> {
454460
Err(anyhow!("Not implemented"))
455461
}
462+
463+
fn start_log_group(&self, name: String) {
464+
log::info!(target: "CI_LOG_GROUPING", "start_log_group: {name}");
465+
}
466+
467+
fn end_log_group(&self) {
468+
log::info!(target: "CI_LOG_GROUPING", "end_log_group");
469+
}
456470
}
457471

458472
#[tokio::test]
459473
async fn dummy_coverage() {
460474
assert!(TestClient::make_headers().is_err());
461475
let dummy = TestClient::default();
476+
dummy.start_log_group("Dummy test".to_string());
462477
assert_eq!(dummy.set_exit_code(1, None, None), 0);
463478
assert!(dummy
464479
.get_list_of_changed_files(&FileFilter::new(&[], vec![]))
@@ -474,7 +489,8 @@ mod test {
474489
assert!(dummy
475490
.post_feedback(&[], FeedbackInput::default())
476491
.await
477-
.is_err())
492+
.is_err());
493+
dummy.end_log_group();
478494
}
479495

480496
// ************************************************* try_next_page() tests

cpp-linter/src/run.rs

+12-6
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use openssl_probe;
1717
use crate::clang_tools::capture_clang_tools_output;
1818
use crate::cli::{get_arg_parser, ClangParams, Cli, FeedbackInput, LinesChangedOnly};
1919
use crate::common_fs::FileFilter;
20-
use crate::logger::{self, end_log_group, start_log_group};
20+
use crate::logger;
2121
use crate::rest_api::{github::GithubApiClient, RestApiClient};
2222

2323
const VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -93,7 +93,7 @@ pub async fn run_main(args: Vec<String>) -> Result<()> {
9393
}
9494
}
9595

96-
start_log_group(String::from("Get list of specified source files"));
96+
rest_api_client.start_log_group(String::from("Get list of specified source files"));
9797
let files = if cli.lines_changed_only != LinesChangedOnly::Off || cli.files_changed_only {
9898
// parse_diff(github_rest_api_payload)
9999
rest_api_client
@@ -124,16 +124,22 @@ pub async fn run_main(args: Vec<String>) -> Result<()> {
124124
log::info!(" ./{}", file.name.to_string_lossy().replace('\\', "/"));
125125
arc_files.push(Arc::new(Mutex::new(file)));
126126
}
127-
end_log_group();
127+
rest_api_client.end_log_group();
128128

129129
let mut clang_params = ClangParams::from(&cli);
130130
let user_inputs = FeedbackInput::from(&cli);
131-
capture_clang_tools_output(&mut arc_files, cli.version.as_str(), &mut clang_params).await?;
132-
start_log_group(String::from("Posting feedback"));
131+
capture_clang_tools_output(
132+
&mut arc_files,
133+
cli.version.as_str(),
134+
&mut clang_params,
135+
&rest_api_client,
136+
)
137+
.await?;
138+
rest_api_client.start_log_group(String::from("Posting feedback"));
133139
let checks_failed = rest_api_client
134140
.post_feedback(&arc_files, user_inputs)
135141
.await?;
136-
end_log_group();
142+
rest_api_client.end_log_group();
137143
if env::var("PRE_COMMIT").is_ok_and(|v| v == "1") {
138144
if checks_failed > 1 {
139145
return Err(anyhow!("Some checks did not pass"));

0 commit comments

Comments
 (0)