#[cfg(feature = "json-log")]
use std::io::{self, Write};
#[cfg(feature = "json-log")]
use std::sync::atomic::{AtomicU64, Ordering};
#[cfg(feature = "json-log")]
static SESSION_ID: AtomicU64 = AtomicU64::new(0);
#[cfg(feature = "json-log")]
pub fn init_session_id() {
let id = (|| -> Option<u64> {
use std::io::Read;
let mut buf = [0u8; 8];
std::fs::File::open("/dev/urandom")
.and_then(|mut f| f.read_exact(&mut buf))
.ok()?;
Some(u64::from_le_bytes(buf))
})()
.unwrap_or_else(|| {
let pid = std::process::id() as u64;
pid.wrapping_mul(0x9e3779b97f4a7c15).wrapping_add(ts_ns())
});
SESSION_ID.store(id, Ordering::Relaxed);
}
#[cfg(feature = "json-log")]
fn ts_ns() -> u64 {
std::time::UNIX_EPOCH
.elapsed()
.map(|d| d.as_nanos().min(u64::MAX as u128) as u64)
.unwrap_or(0)
}
#[cfg(feature = "json-log")]
fn write_json_str(w: &mut impl Write, s: &str) -> io::Result<()> {
w.write_all(b"\"")?;
for &b in s.as_bytes() {
match b {
b'\"' => w.write_all(b"\\\"")?,
b'\\' => w.write_all(b"\\\\")?,
b'\n' => w.write_all(b"\\n")?,
b'\r' => w.write_all(b"\\r")?,
b'\t' => w.write_all(b"\\t")?,
0x00..=0x1F => write!(w, "\\u{:04x}", b)?,
_ => w.write_all(&[b])?,
}
}
w.write_all(b"\"")
}
#[cfg(feature = "json-log")]
pub fn emit_json(
level: &str,
msg: &str,
pid: Option<u32>,
child_pid: Option<u32>,
error: Option<&str>,
) {
let mut stderr = io::stderr().lock();
let session_id = SESSION_ID.load(Ordering::Relaxed);
let _ = write!(
&mut stderr,
"{{\"ts_ns\":{},\"session_id\":\"{:016x}\"",
ts_ns(),
session_id,
);
let _ = stderr.write_all(b",");
let _ = write!(&mut stderr, "\"level\":");
let _ = write_json_str(&mut stderr, level);
let _ = stderr.write_all(b",");
let _ = write!(&mut stderr, "\"msg\":");
let _ = write_json_str(&mut stderr, msg);
if let Some(p) = pid {
let _ = stderr.write_all(b",");
let _ = write!(&mut stderr, "\"pid\":{p}");
}
if let Some(cp) = child_pid {
let _ = stderr.write_all(b",");
let _ = write!(&mut stderr, "\"child_pid\":{cp}");
}
if let Some(e) = error {
let _ = stderr.write_all(b",");
let _ = write!(&mut stderr, "\"error\":");
let _ = write_json_str(&mut stderr, e);
}
let _ = writeln!(&mut stderr, "}}");
}
#[doc(hidden)]
pub struct StackFmt<const N: usize> {
buf: [u8; N],
len: usize,
}
impl<const N: usize> Default for StackFmt<N> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> StackFmt<N> {
#[inline]
pub fn new() -> Self {
Self {
buf: [0u8; N],
len: 0,
}
}
#[inline]
pub fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(&self.buf[..self.len]) }
}
}
impl<const N: usize> core::fmt::Write for StackFmt<N> {
#[inline]
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let remaining = N.saturating_sub(self.len);
if remaining == 0 {
return Ok(());
}
let bytes = s.as_bytes();
if bytes.len() <= remaining {
self.buf[self.len..self.len + bytes.len()].copy_from_slice(bytes);
self.len += bytes.len();
} else {
let mut cut = remaining;
while cut > 0 && (bytes[cut] & 0xC0) == 0x80 {
cut -= 1;
}
self.buf[self.len..self.len + cut].copy_from_slice(&bytes[..cut]);
self.len += cut;
}
Ok(())
}
}
#[macro_export]
macro_rules! varta_info {
($($arg:tt)*) => {{
let mut _buf = $crate::log::StackFmt::<320>::new();
let _ = ::core::fmt::Write::write_fmt(&mut _buf, ::core::format_args!($($arg)*));
#[cfg(feature = "json-log")]
$crate::log::emit_json("info", _buf.as_str(), None, None, None);
#[cfg(not(feature = "json-log"))]
{
let _ = ::std::io::Write::write_fmt(
&mut ::std::io::stderr().lock(),
::core::format_args!("varta-watch: {}\n", _buf.as_str()),
);
}
}};
}
#[macro_export]
macro_rules! varta_warn {
($($arg:tt)*) => {{
let mut _buf = $crate::log::StackFmt::<320>::new();
let _ = ::core::fmt::Write::write_fmt(&mut _buf, ::core::format_args!($($arg)*));
#[cfg(feature = "json-log")]
$crate::log::emit_json("warn", _buf.as_str(), None, None, None);
#[cfg(not(feature = "json-log"))]
{
let _ = ::std::io::Write::write_fmt(
&mut ::std::io::stderr().lock(),
::core::format_args!("varta-watch: {}\n", _buf.as_str()),
);
}
}};
}
#[macro_export]
macro_rules! varta_error {
($($arg:tt)*) => {{
let mut _buf = $crate::log::StackFmt::<320>::new();
let _ = ::core::fmt::Write::write_fmt(&mut _buf, ::core::format_args!($($arg)*));
#[cfg(feature = "json-log")]
$crate::log::emit_json("error", _buf.as_str(), None, None, None);
#[cfg(not(feature = "json-log"))]
{
let _ = ::std::io::Write::write_fmt(
&mut ::std::io::stderr().lock(),
::core::format_args!("varta-watch: {}\n", _buf.as_str()),
);
}
}};
}
#[macro_export]
macro_rules! varta_info_pid {
($pid:expr, $($arg:tt)*) => {{
let mut _buf = $crate::log::StackFmt::<320>::new();
let _ = ::core::fmt::Write::write_fmt(&mut _buf, ::core::format_args!($($arg)*));
#[cfg(feature = "json-log")]
$crate::log::emit_json("info", _buf.as_str(), Some($pid), None, None);
#[cfg(not(feature = "json-log"))]
{
let _ = ::std::io::Write::write_fmt(
&mut ::std::io::stderr().lock(),
::core::format_args!("varta-watch: {}\n", _buf.as_str()),
);
}
}};
}
#[macro_export]
macro_rules! varta_info_pid_child {
($pid:expr, $child_pid:expr, $($arg:tt)*) => {{
let mut _buf = $crate::log::StackFmt::<320>::new();
let _ = ::core::fmt::Write::write_fmt(&mut _buf, ::core::format_args!($($arg)*));
#[cfg(feature = "json-log")]
$crate::log::emit_json("info", _buf.as_str(), Some($pid), Some($child_pid), None);
#[cfg(not(feature = "json-log"))]
{
let _ = ::std::io::Write::write_fmt(
&mut ::std::io::stderr().lock(),
::core::format_args!("varta-watch: {}\n", _buf.as_str()),
);
}
}};
}
#[macro_export]
macro_rules! varta_warn_child {
($child_pid:expr, $($arg:tt)*) => {{
let mut _buf = $crate::log::StackFmt::<320>::new();
let _ = ::core::fmt::Write::write_fmt(&mut _buf, ::core::format_args!($($arg)*));
#[cfg(feature = "json-log")]
$crate::log::emit_json("warn", _buf.as_str(), None, Some($child_pid), None);
#[cfg(not(feature = "json-log"))]
{
let _ = ::std::io::Write::write_fmt(
&mut ::std::io::stderr().lock(),
::core::format_args!("varta-watch: {}\n", _buf.as_str()),
);
}
}};
}
#[macro_export]
macro_rules! varta_error_pid {
($pid:expr, $error:expr, $($arg:tt)*) => {{
let mut _buf = $crate::log::StackFmt::<320>::new();
let mut _err = $crate::log::StackFmt::<128>::new();
let _ = ::core::fmt::Write::write_fmt(&mut _buf, ::core::format_args!($($arg)*));
let _ = ::core::fmt::Write::write_fmt(&mut _err, ::core::format_args!("{}", $error));
#[cfg(feature = "json-log")]
$crate::log::emit_json("error", _buf.as_str(), Some($pid), None, Some(_err.as_str()));
#[cfg(not(feature = "json-log"))]
{
let _ = ::std::io::Write::write_fmt(
&mut ::std::io::stderr().lock(),
::core::format_args!("varta-watch: {}\n", _buf.as_str()),
);
}
}};
}
#[macro_export]
macro_rules! varta_error_err {
($error:expr, $($arg:tt)*) => {{
let mut _buf = $crate::log::StackFmt::<320>::new();
let mut _err = $crate::log::StackFmt::<128>::new();
let _ = ::core::fmt::Write::write_fmt(&mut _buf, ::core::format_args!($($arg)*));
let _ = ::core::fmt::Write::write_fmt(&mut _err, ::core::format_args!("{}", $error));
#[cfg(feature = "json-log")]
$crate::log::emit_json("error", _buf.as_str(), None, None, Some(_err.as_str()));
#[cfg(not(feature = "json-log"))]
{
let _ = ::std::io::Write::write_fmt(
&mut ::std::io::stderr().lock(),
::core::format_args!("varta-watch: {}\n", _buf.as_str()),
);
}
}};
}
#[cfg(test)]
mod tests {
use super::StackFmt;
use core::fmt::Write as _;
#[test]
fn stack_fmt_basic() {
let mut buf = StackFmt::<64>::new();
write!(buf, "hello {}", 42).unwrap();
assert_eq!(buf.as_str(), "hello 42");
}
#[test]
fn stack_fmt_truncates_at_capacity() {
let mut buf = StackFmt::<8>::new();
write!(buf, "hello world").unwrap();
assert_eq!(buf.as_str(), "hello wo");
assert_eq!(buf.as_str().len(), 8);
}
#[test]
fn stack_fmt_truncates_at_utf8_boundary() {
let mut buf = StackFmt::<5>::new();
write!(buf, "ab€").unwrap();
assert_eq!(buf.as_str(), "ab€");
let mut buf2 = StackFmt::<4>::new();
write!(buf2, "ab€").unwrap();
assert_eq!(buf2.as_str(), "ab");
}
#[test]
fn stack_fmt_overflow_does_not_panic() {
let mut buf = StackFmt::<4>::new();
write!(buf, "{:0>100}", 0).unwrap();
assert_eq!(buf.as_str().len(), 4);
}
#[test]
fn stack_fmt_empty() {
let buf = StackFmt::<16>::new();
assert_eq!(buf.as_str(), "");
}
#[test]
fn varta_info_non_json() {
varta_info!("test {}", 42);
varta_warn!("test {}", "warn");
varta_error!("test {}", "err");
varta_info_pid!(1234, "pid {}", 1234);
varta_info_pid_child!(1234, 5678, "pid {} child {}", 1234, 5678);
varta_warn_child!(5678, "child {}", 5678);
varta_error_pid!(
1234,
std::io::Error::from(std::io::ErrorKind::Other),
"pid {} err {}",
1234,
"oops"
);
varta_error_err!(
std::io::Error::from(std::io::ErrorKind::Other),
"err {}",
"oops"
);
}
#[cfg(feature = "json-log")]
mod json_tests {
use super::super::{write_json_str, SESSION_ID};
use std::sync::atomic::Ordering;
#[test]
fn session_id_initializes_without_panic() {
super::super::init_session_id();
let _ = SESSION_ID.load(Ordering::Relaxed);
}
#[test]
fn json_string_escaping() {
let mut buf = Vec::new();
write_json_str(&mut buf, "hello world").unwrap();
assert_eq!(buf, b"\"hello world\"");
}
#[test]
fn json_string_escapes_quotes() {
let mut buf = Vec::new();
write_json_str(&mut buf, "say \"hi\"").unwrap();
assert_eq!(buf, b"\"say \\\"hi\\\"\"");
}
#[test]
fn json_string_escapes_backslash() {
let mut buf = Vec::new();
write_json_str(&mut buf, "path\\to").unwrap();
assert_eq!(buf, b"\"path\\\\to\"");
}
#[test]
fn json_string_escapes_newline() {
let mut buf = Vec::new();
write_json_str(&mut buf, "line1\nline2").unwrap();
assert_eq!(buf, b"\"line1\\nline2\"");
}
#[test]
fn json_string_escapes_control_chars() {
let mut buf = Vec::new();
write_json_str(&mut buf, "a\x01b").unwrap();
assert_eq!(buf, b"\"a\\u0001b\"");
}
}
}