How to show line number and file on tracing events? - rust

How to print what source line! and file! a trace log originated from with tracing and tracing-subscriber?
The code I'm working with is well prepared and contains lots of log prints, which is good, but most of them are not very descriptive:
# Cargo.toml
[dependencies]
tracing = "0.1.35"
tracing-subscriber = "0.3.11"
tracing_subscriber::fmt::init();
// ...
if let Err(e) = important_work.await {
tracing::info!(" {:?}", &e);
};
And the console prints only show module and error message, not where the code failed. When I replaced it with:
pub struct CustomLayer;
impl<S> Layer<S> for CustomLayer
where
S: tracing::Subscriber,
{
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
println!("{level} name={:?}", event.metadata().name());
for field in event.fields() {
println!(" field={}", field);
}
}
}
// Snip to main()
tracing_subscriber::registry().with(CustomLayer).init();
I was able to get the file and line in event.metadata().name()) but then all error messages are turned into just the string "message". There is probably a simpler way of enabling printing of line numbers.

You can customize the formatter as specified in the documentation. Some of the options are with_file() and with_line_number():
tracing_subscriber::fmt()
.event_format(
tracing_subscriber::fmt::format()
.with_file(true)
.with_line_number(true)
)
.init();
Example output (without colors 😔):
2022-07-12T06:05:35.654279Z INFO playground: src/main.rs:16: abc
You can of course use additional options as you wish.

Related

Flatbuffer multiple roots doesn't work as expected

I'm evaluating/learning flatbuffers and I've written a schema and some basic code. The schema contains two root tables but when I try to convert a wrong root it doesn't fail. Is this expected behavior?
schema.fbs:
table Weapon {
name:string;
damage:short;
two_handed:bool;
}
root_type Weapon;
table Shield {
name:string;
damage:short;
}
root_type Shield;
main.rs:
use flatbuffers;
// import the generated code
#[allow(dead_code, unused_imports)]
#[path = "./schema_generated.rs"]
mod schema;
fn main() {
let mut sword_builder = flatbuffers::FlatBufferBuilder::new();
let sword_name = sword_builder.create_string("Sword");
let sword = schema::Weapon::create(
&mut sword_builder,
&schema::WeaponArgs {
name: Some(sword_name),
damage: 10,
two_handed: false,
},
);
sword_builder.finish(sword, None);
let sword_buffer = sword_builder.finished_data();
let mut shield_builder = flatbuffers::FlatBufferBuilder::new();
let shield_name = shield_builder.create_string("Shield");
let shield = schema::Weapon::create(
&mut shield_builder,
&schema::WeaponArgs {
name: Some(shield_name),
damage: 2,
two_handed: true,
},
);
shield_builder.finish(shield, None);
let shield_buffer = shield_builder.finished_data();
// Lets decode our buffers
let sword_decoded = flatbuffers::root::<schema::Weapon>(&sword_buffer).unwrap();
println!("{:#?}", sword_decoded);
let shield_decoded = flatbuffers::root::<schema::Shield>(&shield_buffer).unwrap();
println!("{:#?}", shield_decoded);
// This should fail:
let sword_decoded_failure = flatbuffers::root::<schema::Weapon>(&shield_buffer).unwrap();
println!("{:#?}", sword_decoded_failure);
}
output:
Weapon {
name: Some(
"Sword",
),
damage: 10,
two_handed: false,
}
Shield {
name: Some(
"Shield",
),
damage: 2,
}
Weapon {
name: Some(
"Shield",
),
damage: 2,
two_handed: true,
}
github link: https://github.com/ic3man5/fb_test
Documentation for root:
Gets the root of the Flatbuffer, verifying it first with default options. Note that verification is an experimental feature and may not be maximally performant or catch every error (though that is the goal). See the _unchecked variants for previous behavior.
I would expect it to be able to catch a basic error like this? If so I can see two work arounds, one to prepend a header in front of the bytes to identify the table or using a flatbuffer union (I don't want to do this).
Flatbuffers only allows one root type per schema and one instance of it per buffer. So your schema needs to change to reflect this.
As for there being no error, a verifier takes a binary buffer of bytes and checks that it can be safely accessed according to the current schema. There is no type information in the binary bytes, so if the bytes happen to be safely accessible by another schema, it may succeed. To force it to not succeed, you could add a file_identifier to your schema, which if the Rust verifier checks it, would cause it to fail for the wrong schema.

How to refer to the program name in help strings?

In my CLI program the usage examples are provided as part of the help message. Using the clap derive interface I can do the following
#[derive(Parser, Debug, Default)]
#[clap( after_help = "EXAMPLES:\n $ foo abc.txt")]
pub struct CmdLine {...}
The program name foo is hard coded in the literal string above.
How can I avoid hard-coding the program name and get it dynamically; for example, from std::env::args[0] or clap::App:get_bin_name() ?
clap provides a macro called crate_name! that will take the name from your cargo.toml.
For example, suppose you have this in your cargo.toml.
[package]
name = "myapp"
description = "myapp description"
version = "0.1.0"
edition = "2021"
authors = [ "John Doe" ]
Then, in your application, you can fetch these values using the macros, like this:
let matches = Command::new(clap::crate_name!())
.version(clap::crate_version!())
.author(clap::crate_authors!())
.about(clap::crate_description!())
//
// abbreviated
//
The section below is appended to respond to the original poster's specific question. See the comments below for context. Also, including some learnings as well.
Appended per the discussion in comments.
Based on the comments/discussion below, initial thought is just to stuff the binary name from the arguments into a string and pass into the after_help() function. For example, something like this:
let bin_name = std::env:args().into_iter().next().unwrap();
let matches = Command::new(bin_name)
.after_help(format!("Text that includes {}", bin_name)) // This won't compile
.get_matches();
Taking this approach, you quickly run into a lifetime requirement in the function signature for after_help(). From clap's repo:
pub fn after_help<S: Into<&'help str>>(mut self, help: S)
In fact, if you look, there are many fields in the Command struct that have the lifetime annotation (&'help) on them. The Command::new() method doesn't have this lifetime annotation so it worked fine to just pass it bin_name as shown above.
Below is an abbreviated solution that dynamically generates after-help text in a manner that adheres to the lifetime requirements. Assuming a clean binary (application), called "foo", add the following code:
cargo.toml
[package]
name = "foo"
version = "0.1.0"
description = "A foo cli application"
authors = [ "John Doe" ]
edition = "2021"
[dependencies]
clap = { version = "3.1.6", features = ["cargo"] }
main.rs
fn main() {
// Get the binary name from the command line
let bin_name = std::env::args().into_iter().next().unwrap();
// Construct text that will be used in after_help.
let after_help_text = format!(
"Some after-help text that includes the binary name: {}",
bin_name
);
// clap, by default, will reference the name of your package. So, if you're
// doing the above, you might as well override the usage text too so you're
// being consistent.
let usage_text = format!("{}", bin_name);
if let Err(e) = foo::get_args(bin_name, after_help_text, usage_text).and_then(foo::run) {
eprintln!("{e}");
std::process::exit(1);
}
}
lib.rs
use clap::{ArgMatches, Command};
pub fn get_args(
bin_name: String,
after_help_text: String,
usage_text: String,
) -> std::io::Result<ArgMatches> {
let matches = Command::new(bin_name)
.override_usage(usage_text.as_str())
.version(clap::crate_version!())
.after_help(after_help_text.as_str())
.author(clap::crate_authors!())
.about(clap::crate_description!())
// add and configure args...
.get_matches();
Result::Ok(matches)
}
pub fn run(matches: ArgMatches) -> std::io::Result<()> {
// Do your CLI logic here based on matches.
Ok(())
}
Running the solution ( cargo run -- --help ) will produce the following output:
./foo 0.1.0
John Doe
A foo cli application
USAGE:
./foo
OPTIONS:
-h, --help Print help information
-V, --version Print version information
Some after-help text that includes the binary name: ./foo

How to use mail filter context data?

I am trying to write a mail filter in Rust using the milter crate. I built the example on a Linux VM and it all works fine. However, the example is using u32 as the type of context injected into their handlers, a quite simple example. I instead need to store a string from the handle_header callback through to the handle_eom handler so I can use an incoming header to set the envelope from.
If I log the value of the header in handle_header to console, it writes correctly but by the time it arrives in handle_eom, it has been corrupted/overwritten whatever. I thought that context was supposed to be specifically for this scenario but it seems weird that it uses type inference rather than e.g. a pointer to an object that you can just assign whatever you want to it.
Is my understanding of context wrong or is the code incorrect?
I tried using value and &value in handle_header and it behaves the same way.
use milter::*;
fn main() {
Milter::new("inet:3000#localhost")
.name("BounceRewriteFilter")
.on_header(header_callback)
.on_eom(eom_callback)
.on_abort(abort_callback)
.actions(Actions::ADD_HEADER | Actions::REPLACE_SENDER)
.run()
.expect("milter execution failed");
}
#[on_header(header_callback)]
fn handle_header<'a>(mut context: Context<&'a str>, header: &str, value: &'a str) -> milter::Result<Status> {
if header == "Set-Return-Path" {
match context.data.borrow_mut() {
Some(retpath) => *retpath = &value,
None => {
context.data.replace(value)?;
}
}
}
Ok(Status::Continue)
}
#[on_eom(eom_callback)]
fn handle_eom(mut context: Context<&str>) -> milter::Result<Status> {
match context.data.take() {
Ok(result) => {
println!("Set-return-path header is {}", result.unwrap());
context.api.replace_sender(result.unwrap(), None::<&str>)?;
}
Err(_error) => {}
}
Ok(Status::Continue)
}
Thanks to glts on Github, the author of the crate, the problem was that the string slices passed into the handle_header method were not borrowed by the external code that stores the data pointer so by the time that handle_eom is called, the memory has been reused for something else.
All I had to do was change Context<&str> to Context<String> and convert the strings using mystr.to_owned() and in the reverse direction val = &*mystring

How to handle errors for Not found key in Rust

I'm trying to capture errors on Rust, but I don't want the program to exit if I found it. I'm very new to Rust. Basically, I want to find a key from the Windows registry and if it doesn't exist, then create it.
Im using a crate called winreg for that.
This would be a section of my program:
fn main() -> io::Result<()> {
...
...
...
key.set_value("TestSZ", &"written by Rust")?;
// here I'm getting a value that exists
let sz_val: String = key.get_value("TestSZ")?;
// but this key doesn't exist
let other: String = key.get_value("NOT_EXISTING_KEY")?;
println!("TestSZ = {}", sz_val);
println!("TestSZ = {}", other);
Ok(())
}
If I compile that I receive this in the console:
And now lets write something...
An existing key has been opened
TestSZ = written by Rust
Error: Os { code: 2, kind: NotFound, message: "Couldn't find the pecified file." }
error: process didn't exit successfully: `target\debug\playground.exe` (exit code: 1)
In a pseudocode way, I would like something like:
if other == null {
println!("Nothing found!");
create_key();
}
If I analize get_value it looks like this:
pub fn get_value<T: FromRegValue, N: AsRef<OsStr>>(&self, name: N) -> io::Result<T>
I don't know what that means. I've been reading about errors and everything I do fails.
If I do let other: String = key.get_value("NOT_EXISTING_KEY").expect("Failed to read product name"); then the program exits, showing the error.
But I don't want the program to fail, I want to capture the error and do a different flow if I don't find the key (for example, create it).
Does anyone know how can I deal with this?
In Rust, a function that can fail usually returns a Result<OkType, ErrorType> data type. This type is a structured enum, which means, that it can tell you not only if error has occured, but also what kind of error, so you could act accordingly.
You can process enums with match statements. Or alternatively, Result type has shortcuts like Result::unwrap or Result::expect that basically say: "If there is an error, just tell me what kind and crash the program."
I'm not very familiar with Windows Registry, so I'm not sure how bulletproof the following code snippet is, but it should give you an idea on how you can process errors with a match statement.
fn main() {
// ...
let anykey_value = match key.get_value("AnyKey") {
// If the key is present, initialize `anykey_value` variable
// with the returned value
Ok(value) => value,
// If the key is not found, do the following steps:
Err(error) => {
println!("Nothing found!");
// Try to set an empty string as the value for "AnyKey".
// If fails: panic with the following message.
key.set_value("AnyKey", &"").expect("Failed to create key \"AnyKey\"");
// Initialize `anykey_value` variable with an empty string.
""
}
}
// Will print the value stored in "AnyKey"
// or an empty string, if the key was just created.
println!("AnyKey = {}", anykey_value);
// ...
}
Also you can checkout Error handling chapter from The Rust Programming Language book. It might be helpful.
I got it this way:
match key.get_value("NOT_EXISTING_KEY") {
Ok(value) => {
println!("found: {}", value);
value
},
Err(err) => {
println!("not found: {}", err);
String::from("")},
};
It was expecting a String

Idiomatic rust way to properly parse Clap ArgMatches

I'm learning rust and trying to make a find like utility (yes another one), im using clap and trying to support command line and config file for the program's parameters(this has nothing to do with the clap yml file).
Im trying to parse the commands and if no commands were passed to the app, i will try to load them from a config file.
Now I don't know how to do this in an idiomatic way.
fn main() {
let matches = App::new("findx")
.version(crate_version!())
.author(crate_authors!())
.about("find + directory operations utility")
.arg(
Arg::with_name("paths")
...
)
.arg(
Arg::with_name("patterns")
...
)
.arg(
Arg::with_name("operation")
...
)
.get_matches();
let paths;
let patterns;
let operation;
if matches.is_present("patterns") && matches.is_present("operation") {
patterns = matches.values_of("patterns").unwrap().collect();
paths = matches.values_of("paths").unwrap_or(clap::Values<&str>{"./"}).collect(); // this doesn't work
operation = match matches.value_of("operation").unwrap() { // I dont like this
"Append" => Operation::Append,
"Prepend" => Operation::Prepend,
"Rename" => Operation::Rename,
_ => {
print!("Operation unsupported");
process::exit(1);
}
};
}else if Path::new("findx.yml").is_file(){
//TODO: try load from config file
}else{
eprintln!("Command line parameters or findx.yml file must be provided");
process::exit(1);
}
if let Err(e) = findx::run(Config {
paths: paths,
patterns: patterns,
operation: operation,
}) {
eprintln!("Application error: {}", e);
process::exit(1);
}
}
There is an idiomatic way to extract Option and Result types values to the same scope, i mean all examples that i have read, uses match or if let Some(x) to consume the x value inside the scope of the pattern matching, but I need to assign the value to a variable.
Can someone help me with this, or point me to the right direction?
Best Regards
Personally I see nothing wrong with using the match statements and folding it or placing it in another function. But if you want to remove it there are many options.
There is the ability to use the .default_value_if() method which is impl for clap::Arg and have a different default value depending on which match arm is matched.
From the clap documentation
//sets value of arg "other" to "default" if value of "--opt" is "special"
let m = App::new("prog")
.arg(Arg::with_name("opt")
.takes_value(true)
.long("opt"))
.arg(Arg::with_name("other")
.long("other")
.default_value_if("opt", Some("special"), "default"))
.get_matches_from(vec![
"prog", "--opt", "special"
]);
assert_eq!(m.value_of("other"), Some("default"));
In addition you can add a validator to your operation OR convert your valid operation values into flags.
Here's an example converting your match arm values into individual flags (smaller example for clarity).
extern crate clap;
use clap::{Arg,App};
fn command_line_interface<'a>() -> clap::ArgMatches<'a> {
//Sets the command line interface of the program.
App::new("something")
.version("0.1")
.arg(Arg::with_name("rename")
.help("renames something")
.short("r")
.long("rename"))
.arg(Arg::with_name("prepend")
.help("prepends something")
.short("p")
.long("prepend"))
.arg(Arg::with_name("append")
.help("appends something")
.short("a")
.long("append"))
.get_matches()
}
#[derive(Debug)]
enum Operation {
Rename,
Append,
Prepend,
}
fn main() {
let matches = command_line_interface();
let operation = if matches.is_present("rename") {
Operation::Rename
} else if matches.is_present("prepend"){
Operation::Prepend
} else {
//DEFAULT
Operation::Append
};
println!("Value of operation is {:?}",operation);
}
I hope this helps!
EDIT:
You can also use Subcommands with your specific operations. It all depends on what you want to interface to be like.
let app_m = App::new("git")
.subcommand(SubCommand::with_name("clone"))
.subcommand(SubCommand::with_name("push"))
.subcommand(SubCommand::with_name("commit"))
.get_matches();
match app_m.subcommand() {
("clone", Some(sub_m)) => {}, // clone was used
("push", Some(sub_m)) => {}, // push was used
("commit", Some(sub_m)) => {}, // commit was used
_ => {}, // Either no subcommand or one not tested for...
}

Resources