Skip to content

Commit 77cd976

Browse files
committed
Ensure log levels are configurable and improve logging documentation
1 parent 747bcb0 commit 77cd976

File tree

3 files changed

+122
-31
lines changed

3 files changed

+122
-31
lines changed

docs/plan.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Tasks:
77
- [x] Update the README to include a section on how to convert from pre-commit to rustyhook
88
- [x] Remove the duplicate code in the github actions by consolidating the build steps and deployment steps into a single workflow (the ones that relate to publishing that is)
99
- [x] enable logging to a file or other outputs other than just stdout
10-
- [ ] ensure logging levels are configurable and appropriate for all execution steps
10+
- [x] ensure logging levels are configurable and appropriate for all execution steps
1111
- [ ] enable using rustyhook in this repository as for pre-commit replacement and configure for a rust project
1212
- [ ] replace the use of pip with uv - potentially link to the source code so as to get the benefit without needing a local binary
1313
- [ ] emulate all known pre-commit hooks for use with native execution - all known hooks at https://github.com/pre-commit/pre-commit-hooks

src/logging.rs

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,67 @@
22
//!
33
//! This module provides logging functionality for RustyHook, including
44
//! the ability to log to a file or other outputs in addition to stdout.
5+
//!
6+
//! ## Log Levels
7+
//!
8+
//! The following log levels are available, in order of increasing verbosity:
9+
//!
10+
//! - `error`: Only errors are logged
11+
//! - `warn`: Errors and warnings are logged
12+
//! - `info`: Errors, warnings, and informational messages are logged (default)
13+
//! - `debug`: Errors, warnings, informational messages, and debug messages are logged
14+
//! - `trace`: All messages are logged, including detailed tracing information
15+
//!
16+
//! The log level can be set via:
17+
//! - The `--log-level` command line argument
18+
//! - The `RUSTYHOOK_LOG_LEVEL` environment variable
19+
//! - The `log_level` parameter in the `init` function
20+
//!
21+
//! ## Module-Specific Log Levels
22+
//!
23+
//! You can set different log levels for different modules using the `RUSTYHOOK_LOG_MODULES`
24+
//! environment variable. The format is a comma-separated list of `module=level` pairs.
25+
//!
26+
//! For example:
27+
//!
28+
//! ```
29+
//! # Set the default log level to info
30+
//! export RUSTYHOOK_LOG_LEVEL=info
31+
//!
32+
//! # Set module-specific log levels
33+
//! export RUSTYHOOK_LOG_MODULES=rustyhook::runner=debug,rustyhook::hooks=trace
34+
//! ```
35+
//!
36+
//! This will set the log level for the `rustyhook::runner` module to `debug` and the
37+
//! log level for the `rustyhook::hooks` module to `trace`, while keeping the default
38+
//! log level for all other modules at `info`.
539
640
use std::fs::File;
741
use std::io::Write;
842
use std::path::PathBuf;
9-
use env_logger::{Builder, Env};
43+
use env_logger::Builder;
44+
use log::LevelFilter;
45+
46+
/// Parse a log level string into a LevelFilter
47+
///
48+
/// # Arguments
49+
///
50+
/// * `level` - The log level string to parse
51+
///
52+
/// # Returns
53+
///
54+
/// * `Result<LevelFilter, String>` - The parsed log level or an error message
55+
pub fn parse_log_level(level: &str) -> Result<LevelFilter, String> {
56+
match level.to_lowercase().as_str() {
57+
"error" => Ok(LevelFilter::Error),
58+
"warn" => Ok(LevelFilter::Warn),
59+
"info" => Ok(LevelFilter::Info),
60+
"debug" => Ok(LevelFilter::Debug),
61+
"trace" => Ok(LevelFilter::Trace),
62+
"off" => Ok(LevelFilter::Off),
63+
_ => Err(format!("Invalid log level: {}. Valid levels are: error, warn, info, debug, trace, off", level))
64+
}
65+
}
1066

1167
/// Initialize the logger with the specified configuration
1268
///
@@ -15,15 +71,50 @@ use env_logger::{Builder, Env};
1571
/// * `log_file` - Optional path to a log file. If provided, logs will be written to this file
1672
/// in addition to stdout.
1773
/// * `log_level` - The log level to use. If not provided, defaults to "info".
74+
/// Valid values are: error, warn, info, debug, trace, off
1875
///
1976
/// # Returns
2077
///
2178
/// * `Result<(), String>` - Ok if the logger was initialized successfully, Err otherwise
79+
///
80+
/// # Examples
81+
///
82+
/// ```
83+
/// use rustyhook::logging;
84+
/// use std::path::PathBuf;
85+
///
86+
/// // Initialize with default log level (info)
87+
/// logging::init(None, None).unwrap();
88+
///
89+
/// // Initialize with debug log level
90+
/// logging::init(None, Some("debug")).unwrap();
91+
///
92+
/// // Initialize with log file
93+
/// logging::init(Some(PathBuf::from("rustyhook.log")), Some("info")).unwrap();
94+
/// ```
2295
pub fn init(log_file: Option<PathBuf>, log_level: Option<&str>) -> Result<(), String> {
23-
let env = Env::default()
24-
.filter_or("RUSTYHOOK_LOG_LEVEL", log_level.unwrap_or("info"));
96+
// Get the log level from the parameter or environment variable
97+
let level_str = match log_level {
98+
Some(level) => level.to_string(),
99+
None => std::env::var("RUSTYHOOK_LOG_LEVEL").unwrap_or_else(|_| "info".to_string())
100+
};
101+
102+
// Check for module-specific log levels in the environment
103+
let module_filter = std::env::var("RUSTYHOOK_LOG_MODULES").ok();
25104

26-
let mut builder = Builder::from_env(env);
105+
// Parse and validate the log level
106+
let level_filter = parse_log_level(&level_str)?;
107+
108+
// Create a builder with the validated log level
109+
let mut builder = Builder::new();
110+
111+
// Apply module-specific log levels if provided
112+
if let Some(filter) = module_filter {
113+
builder.parse_filters(&filter);
114+
} else {
115+
// Otherwise, apply the global log level
116+
builder.filter_level(level_filter);
117+
}
27118

28119
// Set the default format
29120
builder.format(|buf, record| {

tests/precommit_config_tests.rs

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ fn test_find_precommit_config() {
1010
// The test should find the .pre-commit-config.yaml file in the project root
1111
let result = find_precommit_config();
1212
assert!(result.is_ok());
13-
13+
1414
let config_path = result.unwrap();
1515
assert!(config_path.exists());
1616
assert_eq!(config_path.file_name().unwrap(), ".pre-commit-config.yaml");
@@ -20,62 +20,62 @@ fn test_find_precommit_config() {
2020
fn test_convert_to_rustyhook_config() {
2121
// Find the pre-commit config
2222
let precommit_config_path = find_precommit_config().unwrap();
23-
23+
2424
// Read the pre-commit config
2525
let precommit_config_str = fs::read_to_string(&precommit_config_path).unwrap();
26-
26+
2727
// Convert to RustyHook config
2828
let rustyhook_config = convert_to_rustyhook_config(&precommit_config_str);
29-
29+
3030
// Check that the conversion was successful
3131
assert_eq!(rustyhook_config.default_stages, vec!["commit".to_string(), "push".to_string()]);
3232
assert_eq!(rustyhook_config.fail_fast, true);
33-
33+
3434
// Check that all repositories were converted
3535
assert!(rustyhook_config.repos.len() >= 5); // We have 5 repos in our test config
36-
36+
3737
// Check that the pre-commit-hooks repo was converted correctly
3838
let precommit_hooks_repo = rustyhook_config.repos.iter()
3939
.find(|repo| repo.repo == "https://github.com/pre-commit/pre-commit-hooks")
4040
.expect("pre-commit-hooks repo not found");
41-
41+
4242
assert_eq!(precommit_hooks_repo.hooks.len(), 4); // We have 4 hooks in this repo
43-
43+
4444
// Check that the trailing-whitespace hook was converted correctly
4545
let trailing_whitespace_hook = precommit_hooks_repo.hooks.iter()
4646
.find(|hook| hook.id == "trailing-whitespace")
4747
.expect("trailing-whitespace hook not found");
48-
48+
4949
assert_eq!(trailing_whitespace_hook.name, "Trim Trailing Whitespace");
5050
assert_eq!(trailing_whitespace_hook.stages, vec!["commit".to_string()]);
51-
51+
5252
// Check that the Rust repo was converted correctly
5353
let rust_repo = rustyhook_config.repos.iter()
5454
.find(|repo| repo.repo == "https://github.com/doublify/pre-commit-rust")
5555
.expect("Rust repo not found");
56-
56+
5757
assert_eq!(rust_repo.hooks.len(), 2); // We have 2 hooks in this repo
58-
58+
5959
// Check that the fmt hook was converted correctly
6060
let fmt_hook = rust_repo.hooks.iter()
6161
.find(|hook| hook.id == "fmt")
6262
.expect("fmt hook not found");
63-
63+
6464
assert_eq!(fmt_hook.name, "Rust Formatter");
6565
assert_eq!(fmt_hook.language, "rust");
66-
66+
6767
// Check that the local repo was converted correctly
6868
let local_repo = rustyhook_config.repos.iter()
6969
.find(|repo| repo.repo == "local")
7070
.expect("local repo not found");
71-
71+
7272
assert_eq!(local_repo.hooks.len(), 2); // We have 2 hooks in this repo
73-
73+
7474
// Check that the custom-python-script hook was converted correctly
7575
let custom_python_hook = local_repo.hooks.iter()
7676
.find(|hook| hook.id == "custom-python-script")
7777
.expect("custom-python-script hook not found");
78-
78+
7979
assert_eq!(custom_python_hook.name, "Custom Python Script");
8080
assert_eq!(custom_python_hook.language, "python");
8181
assert!(custom_python_hook.additional_dependencies.contains(&"requests==2.28.2".to_string()));
@@ -86,26 +86,26 @@ fn test_convert_from_precommit() {
8686
// Create a temporary directory for the test
8787
let temp_dir = tempfile::tempdir().unwrap();
8888
let original_dir = env::current_dir().unwrap();
89-
89+
9090
// Copy the .pre-commit-config.yaml file to the temporary directory
9191
let precommit_config_path = original_dir.join(".pre-commit-config.yaml");
9292
let temp_precommit_config_path = temp_dir.path().join(".pre-commit-config.yaml");
9393
fs::copy(&precommit_config_path, &temp_precommit_config_path).unwrap();
94-
94+
9595
// Change to the temporary directory
9696
env::set_current_dir(&temp_dir).unwrap();
97-
97+
9898
// Convert from pre-commit config
99-
let result = rustyhook::config::convert_from_precommit::<&str>(None, None);
99+
let result = rustyhook::config::convert_from_precommit::<&str>(None, None, false);
100100
assert!(result.is_ok());
101-
101+
102102
// Check that the .rustyhook/config.yaml file was created
103103
let rustyhook_config_path = temp_dir.path().join(".rustyhook").join("config.yaml");
104104
assert!(rustyhook_config_path.exists());
105-
105+
106106
// Read the RustyHook config
107107
let rustyhook_config_str = fs::read_to_string(&rustyhook_config_path).unwrap();
108-
108+
109109
// Check that the conversion was successful
110110
assert!(rustyhook_config_str.contains("default_stages: [commit, push]"));
111111
assert!(rustyhook_config_str.contains("fail_fast: true"));
@@ -115,7 +115,7 @@ fn test_convert_from_precommit() {
115115
assert!(rustyhook_config_str.contains("id: fmt"));
116116
assert!(rustyhook_config_str.contains("repo: local"));
117117
assert!(rustyhook_config_str.contains("id: custom-python-script"));
118-
118+
119119
// Change back to the original directory
120120
env::set_current_dir(original_dir).unwrap();
121-
}
121+
}

0 commit comments

Comments
 (0)