Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions examples/journal-try-send.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#![warn(rust_2018_idioms)]

#[cfg(feature = "journal")]
mod x {
//! Demonstrate the new Result-based journal API with proper error handling.

use systemd::journal;

pub fn main() {
println!("Demonstrating new Result-based journal API...");

// Using try_send for detailed control
match journal::try_send(&[
"MESSAGE=Hello from Rust with Result API!",
"PRIORITY=6",
"CODE_FILE=journal-send-result.rs",
"CODE_LINE=15",
"CUSTOM_FIELD=test_value",
]) {
Ok(()) => println!("Detailed message sent successfully"),
Err(e) => println!("Failed to send detailed message: {}", e),
}

// Using try_print for simple messages
if let Err(e) = journal::try_print(6, "Simple message with Result API") {
println!("Failed to send simple message: {}", e);
} else {
println!("Simple message sent successfully");
}

// Using try_log for structured logging
match journal::try_log(
4, // Warning level
file!(),
line!(),
module_path!(),
&format_args!("Structured log entry with count: {}", 42),
) {
Ok(()) => println!("Structured log sent successfully"),
Err(e) => println!("Failed to send structured log: {}", e),
}

// Demonstrating error handling with invalid data
// This should still succeed as systemd is quite permissive
match journal::try_send(&["INVALID_KEY_WITHOUT_VALUE"]) {
Ok(()) => println!("Even invalid-looking data was accepted"),
Err(e) => println!("Invalid data was rejected: {}", e),
}

// Chaining operations with proper error handling
let operations = [
|| journal::try_print(3, "Error level message"),
|| journal::try_print(4, "Warning level message"),
|| journal::try_print(6, "Info level message"),
|| journal::try_print(7, "Debug level message"),
];

let mut success_count = 0;
for (i, op) in operations.iter().enumerate() {
match op() {
Ok(()) => {
success_count += 1;
println!("Operation {} succeeded", i + 1);
}
Err(e) => println!("Operation {} failed: {}", i + 1, e),
}
}

println!(
"Summary: {}/{} operations succeeded",
success_count,
operations.len()
);
println!("Example completed. Check your journal with: journalctl -t journal-send-result");
}
}

#[cfg(not(feature = "journal"))]
mod x {
pub fn main() {
println!("This example requires the 'journal' feature.");
println!("Run with: cargo run --example journal-send-result --features journal");
}
}

fn main() {
x::main()
}
84 changes: 79 additions & 5 deletions src/journal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use std::os::raw::c_void;
use std::os::unix::io::AsRawFd;
use std::{fmt, io, ptr, result, slice, time};

fn collect_and_send<T, S>(args: T) -> c_int
fn try_collect_and_send<T, S>(args: T) -> Result<()>
where
T: Iterator<Item = S>,
S: AsRef<str>,
Expand All @@ -27,22 +27,66 @@ where
// the data it's referencing in order to avoid additional allocations.
.map(|x| unsafe { const_iovec::from_str(x) })
.collect();
unsafe { ffi::sd_journal_sendv(iovecs.as_ptr(), iovecs.len() as c_int) }

let iovecs_len: c_int = iovecs.len().try_into().map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"Too many iovecs for systemd journal"
)
})?;

let result = unsafe { ffi::sd_journal_sendv(iovecs.as_ptr(), iovecs_len) };
ffi_result(result).map(|_| ())
}

fn collect_and_send<T, S>(args: T) -> c_int
where
T: Iterator<Item = S>,
S: AsRef<str>,
{
try_collect_and_send(args).map_or_else(|_| -1, |_| 0)
}

/// Send preformatted fields to systemd.
///
/// This is a relatively low-level operation and probably not suitable unless
/// you need precise control over which fields are sent to systemd.
#[deprecated(
since = "0.11.0",
note = "Use `try_send` instead for proper error handling"
)]
pub fn send(args: &[&str]) -> c_int {
collect_and_send(args.iter())
}


/// Send preformatted fields to systemd.
///
/// This is a relatively low-level operation and probably not suitable unless
/// you need precise control over which fields are sent to systemd.
///
/// Returns `Ok(())` on success, or an `Error` on failure.
pub fn try_send(args: &[&str]) -> Result<()> {
try_collect_and_send(args.iter())
}

/// Send a simple message to systemd-journald.
#[deprecated(
since = "0.11.0",
note = "Use `try_print` instead for proper error handling"
)]
pub fn print(lvl: u32, s: &str) -> c_int {
send(&[&format!("PRIORITY={lvl}"), &format!("MESSAGE={s}")])
}


/// Send a simple message to systemd-journald.
///
/// Returns `Ok(())` on success, or an `Error` on failure.
pub fn try_print(lvl: u32, s: &str) -> Result<()> {
try_send(&[&format!("PRIORITY={lvl}"), &format!("MESSAGE={s}")])
}

enum SyslogLevel {
// Emerg = 0,
// Alert = 1,
Expand All @@ -67,18 +111,47 @@ impl From<log::Level> for SyslogLevel {
}

/// Record a log entry, with custom priority and location.
#[deprecated(
since = "0.11.0",
note = "Use `try_log` instead for proper error handling"
)]
pub fn log(level: usize, file: &str, line: u32, module_path: &str, args: &fmt::Arguments<'_>) {
send(&[
let _ = try_log(level, file, line, module_path, args);
}

/// Record a log entry, with custom priority and location.
///
/// Returns `Ok(())` on success, or an `Error` on failure.
pub fn try_log(
level: usize,
file: &str,
line: u32,
module_path: &str,
args: &fmt::Arguments<'_>,
) -> Result<()> {
try_send(&[
&format!("PRIORITY={level}"),
&format!("MESSAGE={args}"),
&format!("CODE_LINE={line}"),
&format!("CODE_FILE={file}"),
&format!("CODE_MODULE={module_path}"),
]);
])
}


/// Send a `log::Record` to systemd-journald.
#[deprecated(
since = "0.11.0",
note = "Use `try_log_record` instead for proper error handling"
)]
pub fn log_record(record: &Record<'_>) {
let _ = try_log_record(record);
}

/// Send a `log::Record` to systemd-journald.
///
/// Returns `Ok(())` on success, or an `Error` on failure.
pub fn try_log_record(record: &Record<'_>) -> Result<()> {
let keys = [
format!("PRIORITY={}", SyslogLevel::from(record.level()) as usize),
format!("MESSAGE={}", record.args()),
Expand All @@ -90,9 +163,10 @@ pub fn log_record(record: &Record<'_>) {
record.module_path().map(|path| format!("CODE_FUNC={path}")),
];

collect_and_send(keys.iter().chain(opt_keys.iter().flatten()));
try_collect_and_send(keys.iter().chain(opt_keys.iter().flatten()))
}


/// Logger implementation over systemd-journald.
pub struct JournalLog;
impl Log for JournalLog {
Expand Down
38 changes: 38 additions & 0 deletions tests/journal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ fn have_journal() -> bool {
}

#[test]
#[allow(deprecated)]
fn test() {
journal::send(&["CODE_FILE=HI", "CODE_LINE=1213", "CODE_FUNCTION=LIES"]);
journal::print(1, &format!("Rust can talk to the journal: {}", 4));
Expand All @@ -35,6 +36,41 @@ fn test() {
sd_journal_log!(4, "HI {:?}", 2);
}

#[test]
fn test_try_api() {
// Test the new try-based API
journal::try_send(&["CODE_FILE=HI", "CODE_LINE=1213", "CODE_FUNCTION=LIES"]).unwrap();
journal::try_print(1, &format!("Rust can talk to the journal: {}", 4)).unwrap();

// Test that the functions return Ok(()) on success
let result1 = journal::try_send(&["MESSAGE=test message"]);
assert!(result1.is_ok());

let result2 = journal::try_print(6, "test print");
assert!(result2.is_ok());

// Test try_log function
let result3 = journal::try_log(6, "test.rs", 42, "test_module", &format_args!("test log entry"));
assert!(result3.is_ok());
}

#[test]
fn test_try_api_with_special_chars() {
let result = journal::try_send(&[
"MESSAGE=Test with special chars: éàü©",
"CUSTOM=Line\nbreak",
]);
assert!(result.is_ok());
}

#[test]
fn test_empty_args() {
// Test with an empty array
let result = journal::try_send(&[]);
// Systemd should accept this
assert!(result.is_ok());
}

#[test]
fn cursor() {
if !have_journal() {
Expand Down Expand Up @@ -117,6 +153,7 @@ fn test_seek() {
}

#[test]
#[allow(deprecated)]
fn test_simple_match() {
if !have_journal() {
return;
Expand Down Expand Up @@ -172,6 +209,7 @@ fn test_simple_match() {
}

#[test]
#[allow(deprecated)]
fn get_data() {
if !have_journal() {
return;
Expand Down