Skip to content

Commit

Permalink
feat(logging): log! macro and various other improvements (#4588)
Browse files Browse the repository at this point in the history
* feat(logging): allow passing anything convertible to a `String`

This slightly improves the ergonomics of the `set_app_name` function.

* fix(logging): ensure log JSON is followed immediately by newline

Without this a multithreaded application could interleave log messages.

* feat(logging): add `log!` macro to reduce logging boilerplate

Makes a couple other small changes at clippy's behest.
  • Loading branch information
eventualbuddha authored Feb 8, 2024
1 parent 3931dfe commit 5bb53e5
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 21 deletions.
35 changes: 23 additions & 12 deletions libs/logging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,31 @@ try {

```rs
use vx_logging::{
print_log, set_app_name,
types::{EventType, Log},
log, set_app_name,
Disposition, EventId,
EventType, Log,
};
set_app_name("VxAppName".to_string());

fn do_something() {
// ...
print_log(Log {
event_id: EventId::ImportDataComplete,
event_type: EventType::UserAction,
disposition: Disposition::Success,
..Default::default()
});

// run this once at the start of the application
set_app_name("VxAppName");

fn import_something(file_name: &PathBuf) {
log!(EventId::ImportDataInit, "starting to import some data from file: {file_name}");

match do_import(file_name) {
Ok(_) => log!(
event_id: EventId::ImportDataComplete,
event_type: EventType::UserAction,
disposition: Disposition::Success,
message: format!("Imported data from file: {file_name}"),
),
Err(e) => log!(
event_id: EventId::ImportDataComplete,
event_type: EventType::UserAction,
disposition: Disposition::Failure,
message: format!("Error importing data from file: {file_name} ({e})"),
),
}
}
```

Expand Down
78 changes: 78 additions & 0 deletions libs/logging/bacon.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# This is a configuration file for the bacon tool
# More info at https://github.com/Canop/bacon

default_job = "check"

[jobs]

[jobs.check]
command = ["cargo", "check", "--color", "always"]
need_stdout = false

[jobs.check-all]
command = ["cargo", "check", "--all-targets", "--color", "always"]
need_stdout = false
watch = ["tests", "benches", "examples"]

[jobs.clippy]
command = [
"cargo",
"clippy",
"--color",
"always",
"--",
"-W",
"clippy::pedantic",
"-W",
"clippy::nursery",
"-W",
"clippy::unwrap_used",
"-A",
"clippy::cast-possible-truncation",
"-A",
"clippy::cast-possible-wrap",
]
need_stdout = false
watch = ["types-rust"]

[jobs.clippy-all]
command = ["cargo", "clippy", "--all-targets", "--color", "always"]
need_stdout = false
watch = ["tests", "benches", "examples"]

[jobs.test]
command = ["cargo", "test", "--color", "always"]
need_stdout = true
watch = ["tests"]

[jobs.doc]
command = ["cargo", "doc", "--color", "always", "--no-deps"]
need_stdout = false

# if the doc compiles, then it opens in your browser and bacon switches
# to the previous job
[jobs.doc-open]
command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"]
need_stdout = false
on_success = "back" # so that we don't open the browser at each change

# You can run your application and have the result displayed in bacon,
# *if* it makes sense for this crate. You can run an example the same
# way. Don't forget the `--color always` part or the errors won't be
# properly parsed.
[jobs.run]
command = ["cargo", "run", "--color", "always"]
need_stdout = true
allow_warnings = true

# You may define here keybindings that would be specific to
# a project, for example a shortcut to launch a specific job.
# Shortcuts to internal functions (scrolling, toggling, etc.)
# should go in your personal prefs.toml file instead.
[keybindings]
a = "job:check-all"
i = "job:initial"
c = "job:clippy"
d = "job:doc-open"
t = "job:test"
r = "job:run"
2 changes: 1 addition & 1 deletion libs/logging/scripts/log_event_enums.rs.template
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Do not edit this file directly! It's generated and manual changes will be overwritten.
//! To add a log event, edit log_event_details.toml and run `pnpm build:generate-types`.
//! To add a log event, edit `log_event_details.toml` and run `pnpm build:generate-types`.

use serde::{Deserialize, Serialize};
119 changes: 113 additions & 6 deletions libs/logging/types-rust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,126 @@
use std::io::Write;
use std::{io::stdout, sync::OnceLock};
pub(crate) static APP_NAME: OnceLock<String> = OnceLock::new();

pub fn print_log(log: Log) {
match serde_json::to_writer(stdout(), &log) {
Ok(_) => println!(),
/// Prints a log to stdout in JSON format. You probably want to use the `log!` macro instead of
/// calling this directly.
///
/// # Example
///
/// ```
/// use vx_logging::{print_log, Log, EventId, set_app_name};
///
/// set_app_name("my-app");
/// print_log(&Log {
/// event_id: EventId::AuthLogin,
/// message: format!("User {user} logged in", user = "test-user"),
/// ..Default::default()
/// });
pub fn print_log(log: &Log) {
let mut stdout = stdout().lock();
match serde_json::to_writer(&mut stdout, &log) {
Ok(()) => {
let _ = writeln!(&mut stdout);
}
Err(e) => eprintln!("Error serializing log: {e}"),
}
};
}

/// Log a message with the given event id.
///
/// # Example
///
/// ```
/// use vx_logging::{log, set_app_name, Disposition, EventId};
///
/// set_app_name("my-app");
///
/// // shorthand versions: event ID and optional message
/// log!(EventId::MachineBootComplete);
/// log!(EventId::AuthLogin, "User {user} logged in", user = "test-user");
///
/// // full version: specify any set of fields by name
/// log!(
/// message: format!("User {user} blocked!", user = "bad-user"),
/// event_id: EventId::AuthLogin,
/// disposition: Disposition::Failure
/// );
/// ```
#[macro_export]
macro_rules! log {
($event_id:expr) => {
$crate::print_log(&$crate::Log {
event_id: $event_id,
..Default::default()
});
};
($event_id:expr, $($format_args:tt)*) => {
$crate::print_log(&$crate::Log {
event_id: $event_id,
message: format!($($format_args)*),
..Default::default()
});
};
($($arg:tt)*) => {
$crate::print_log(&$crate::Log {
$($arg)*,
..Default::default()
});
};
}

pub fn set_app_name(app_name: String) {
APP_NAME.set(app_name).unwrap();
/// Set the app name to be used in logs for this process.
///
/// # Example
///
/// ```
/// use vx_logging::{log, set_app_name, EventId};
///
/// set_app_name("my-app");
/// log!(EventId::AuthLogin, "User {user} logged in", user = "test-user");
/// ```
pub fn set_app_name(app_name: impl Into<String>) {
if let Err(e) = APP_NAME.set(app_name.into()) {
eprintln!("Error setting app name: {e}");
}
}

mod log_event_enums;
pub use self::log_event_enums::EventId;

pub mod types;
pub use self::types::{Disposition, EventType, Log, User};

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_set_app_name() {
set_app_name("test-app");
assert_eq!(APP_NAME.get().unwrap(), "test-app");
}

#[test]
fn test_print_log() {
set_app_name("test-app");

print_log(&Log::default());
}

#[test]
fn test_log_macro() {
set_app_name("test-app");

log!(EventId::MachineBootComplete);

let user = "test-user";
log!(EventId::AuthLogin, "somebody logged in: {user}");

log!(
message: format!("here is a log message: {}", "what!"),
event_id: EventId::AuthLogin,
disposition: Disposition::Failure
);
}
}
2 changes: 1 addition & 1 deletion libs/logging/types-rust/src/log_event_enums.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Do not edit this file directly! It's generated and manual changes will be overwritten.
//! To add a log event, edit log_event_details.toml and run `pnpm build:generate-types`.
//! To add a log event, edit `log_event_details.toml` and run `pnpm build:generate-types`.
use serde::{Deserialize, Serialize};

Expand Down
2 changes: 1 addition & 1 deletion libs/logging/types-rust/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ impl Default for Log {
user: User::System,
event_id: EventId::Unspecified,
event_type: EventType::SystemStatus,
message: "".to_string(),
message: String::new(),
disposition: Disposition::NA,
}
}
Expand Down

0 comments on commit 5bb53e5

Please sign in to comment.