Rust Instrumentation Library
Instrumentation is crucial for understanding the performance and behavior of software. This challenge asks you to create a simple instrumentation library in Rust that allows developers to track function execution times and log messages with configurable levels. This library will provide a foundation for more complex monitoring and debugging tools.
Problem Description
You are tasked with building a basic instrumentation library named rust_instrumentation. This library should provide the following functionalities:
- Function Timing: A macro
timeit!that measures and logs the execution time of a given function. The macro should accept a function as an argument and print the function name and its execution time to the console. - Logging: A
Loggerstruct with alogmethod. Thelogmethod should accept a log level (e.g.,INFO,DEBUG,ERROR) and a message. The logger should only print messages with a log level equal to or lower than a configured level. - Configurable Log Level: The
Loggerstruct should have aset_levelmethod to change the configured log level.
Key Requirements:
- The
timeit!macro should be easy to use and minimally intrusive. - The
Loggershould provide a clear and concise interface for logging messages. - The log level configuration should be flexible and allow for different levels of verbosity.
- The library should be thread-safe.
Expected Behavior:
- When
timeit!is used, the execution time of the enclosed function should be measured accurately and printed to the console in a readable format (e.g., "Function 'my_function' took 1.234ms"). - The
Loggershould only print messages with a log level that is less than or equal to the configured log level. - Calling
set_levelshould update the log level and affect subsequent log messages.
Edge Cases to Consider:
- Functions with very short execution times (measurements might be inaccurate).
- Functions that panic. The
timeit!macro should handle panics gracefully (e.g., by printing an error message). - Concurrent access to the
Loggerfrom multiple threads.
Examples
Example 1:
// Assuming rust_instrumentation is added as a dependency
use rust_instrumentation::{timeit, Logger};
#[timeit]
fn my_function() -> i32 {
let mut sum = 0;
for i in 1..=100000 {
sum += i;
}
sum
}
fn main() {
let logger = Logger::new();
logger.set_level(rust_instrumentation::LogLevel::Debug);
logger.log(rust_instrumentation::LogLevel::Info, "Starting program");
logger.log(rust_instrumentation::LogLevel::Debug, "Debug message");
logger.log(rust_instrumentation::LogLevel::Error, "An error occurred");
my_function();
}
Output:
Function 'my_function' took 1.234ms
Starting program
Debug message
An error occurred
Explanation: my_function's execution time is printed. The logger prints all messages because the level is set to Debug.
Example 2:
use rust_instrumentation::{timeit, Logger};
#[timeit]
fn panicking_function() {
panic!("This function panics!");
}
fn main() {
let logger = Logger::new();
logger.set_level(rust_instrumentation::LogLevel::Info);
panicking_function();
}
Output:
Function 'panicking_function' panicked!
Explanation: The timeit! macro still measures the time before the panic and prints a message indicating the panic.
Constraints
- Performance: The instrumentation should have minimal overhead on the target code. Avoid unnecessary allocations or complex operations within the
timeit!macro. - Input Format: The log level should be an enum with at least
INFO,DEBUG, andERRORvariants. - Thread Safety: The
Loggermust be thread-safe, allowing multiple threads to log messages concurrently without data races. Use appropriate synchronization primitives (e.g.,Mutex). - Dependencies: You are allowed to use the standard library and external crates for timing (e.g.,
std::time) and synchronization (e.g.,std::sync::Mutex). Avoid using complex logging frameworks.
Notes
- Consider using procedural macros to implement the
timeit!macro. - The
Loggerstruct can use aMutexto protect its internal state from concurrent access. - Focus on the core functionality of timing and logging. Error handling and advanced features (e.g., configurable output format) are not required for this challenge.
- Think about how to handle panics within the timed function gracefully. You don't need to recover from the panic, but you should ensure that the instrumentation doesn't crash the program.
- The
timeit!macro should not modify the original function's signature or behavior beyond measuring its execution time.