I did not build this little utils crate because I thought I had something revolutionary to show.
I built it for a much simpler reason: while working on small Rust projects, I keep running into the same tiny frictions over and over again.
Nothing dramatic. Just the usual stuff:
- logging that starts inconsistent
- little helper code repeated in the wrong places
- config or HTTP glue leaking into feature modules
- small annoyances that are easy to ignore for a day and then annoying for a month
So this crate is mostly me trying to be a bit kinder to my future self.
I wanted one place for the small things that make the project easier to read, easier to debug, and a little less noisy.
And I wanted to share it for the exact same reason.
Not because these files are perfect, and not because I think anyone should copy them as-is, but because I always enjoy seeing how other people solve these very ordinary problems in their own projects.
Sometimes the most useful things to share are not big architectures or polished libraries.
Sometimes they are just small, honest utilities that made a project nicer to work on.
This is the crate I am talking about:
src/
└── utils/
├── helpers.rs
├── macros.rs
├── mod.rs
├── save_load.rs
└── web.rs
So before getting into the rest of the series, I wanted to show the shape of it.
It is a small crate, but each file has its own job:
-
macros.rsfor logs, timestamps, and source breadcrumbs -
helpers.rsfor small error and JSON helpers -
save_load.rsfor config-like persistence -
web.rsfor a thin HTTP layer -
mod.rsas the glue that exposes the submodules
If I had to choose where this little series really starts, it would be macros.rs.
Not because macros are the most important thing in the world, and not because this file is especially smart.
It is just the first file that makes the rest of the project easier to live with.
When I am building a small bot or service, I usually want a few very basic things:
- log lines that look consistent
- timestamps without repeating formatting code everywhere
- a cheap way to see where something happened
That is all src/utils/macros.rs is trying to do.
The real log! macro
Here is the actual code:
#[macro_export]
macro_rules! log {
() => {{
$crate::log!("");
}};
($($arg:tt)*) => {{
std::println!(
"[ {} ] {} {}",
$crate::timestamp!(millis),
if cfg!(debug_assertions){crate::here!()} else {format!("[ {:<20} ]", module_path!())},
format!($($arg)*)
);
}};
}
And the stderr version:
#[macro_export]
macro_rules! err_log {
() => {{
$crate::err_log!("");
}};
($($arg:tt)*) => {{
std::eprintln!(
"[ {} ] {} {}",
$crate::timestamp!(millis),
if cfg!(debug_assertions){crate::here!()} else {format!("[ {:<20} ]", module_path!())},
format!($($arg)*)
);
}};
}
This is not a grand logging system.
It is just a format I do not have to think about every time I want to print something.
That matters more than it sounds.
When the friction is low, I log more consistently. When I log more consistently, I understand the project better.
The small trick I still like
This part is probably my favorite:
if cfg!(debug_assertions){crate::here!()} else {format!("[ {:<20} ]", module_path!())}
In debug builds, I get source position.
In release builds, I get the module path instead.
That feels modest, but useful:
- during development I want precise breadcrumbs
- outside development I usually want something a bit cleaner
I do not need this to be perfect. I just need it to help.
here! is tiny, but honest
This is the real macro:
#[macro_export]
macro_rules! here {
() => {
format!(
"[ {:<30} ]",
format!("{}:{}:{}", file!(), line!(), column!())
)
};
($fmt:literal $(, $arg:expr)* $(,)?) => {
format!(
"[ {:<30} ] {}",
format!("{}:{}:{}", file!(), line!(), column!()),
format_args!($fmt $(, $arg)*)
)
};
}
I use it directly in places like this:
let session_id = msg.payload.session.as_ref().context(here!()).unwrap().id.as_str();
That line is not fancy at all. It is basically me saying:
"If this breaks, please show me exactly where I made this assumption."
I like helpers that are honest like that.
Timestamp helpers
The timestamp macro is also deliberately small:
#[macro_export]
macro_rules! timestamp {
() => {{ chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string() }};
(milliseconds) => {{ chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f").to_string() }};
(millis) => {{ $crate::timestamp!(milliseconds) }};
(micro) => {{ chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.6f").to_string() }};
(micros) => {{ $crate::timestamp!(micro) }};
(nano) => {{ chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.9f").to_string() }};
(nanos) => {{ $crate::timestamp!(nano) }};
($fmt:literal) => {{ chrono::Local::now().format($fmt).to_string() }};
($unknown:tt) => {
compile_error!(
"unsupported timestamp! argument. Use: milliseconds|millis|micro|micros|nano|nanos|\"custom fmt\""
);
};
}
I am aware this is not revolutionary code.
That is kind of the point.
It saves me from repeating chrono formatting noise, and it keeps the call sites readable:
log!("Token will expire at: {:#?}", TW_TOKEN.expires_at().await);
For this project, that is enough justification.
What the output looks like
In debug builds, the output includes the exact source location:
[ 2026-04-30 12:15:44.381 ] [ src/tw_client.rs:94:36 ] Handling session welcome message, id abc123
In release builds, the same kind of line becomes a bit cleaner and shows the module path instead:
[ 2026-04-30 12:15:44.381 ] [ botox::tw_client ] Handling session welcome message, id abc123
That difference is small, but it is intentional.
When I am debugging, I usually want the exact place.
When I am not, the module name is often enough.
It is not beautiful. It is not trying to be.
It is just useful in the exact way I need.
Moving to Part 2
Once I have logs and source breadcrumbs, the next annoyance is usually error context and JSON glue.
That is where helpers.rs starts to matter, so that is the next part.
Top comments (0)