From c9a221369b17392a1b808ff068981e0b9138aa9b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 21:33:11 +0000 Subject: [PATCH 1/6] chore(deps): bump regorus from 0.1.3 to 0.1.4 Bumps [regorus](https://github.com/microsoft/regorus) from 0.1.3 to 0.1.4. - [Release notes](https://github.com/microsoft/regorus/releases) - [Changelog](https://github.com/microsoft/regorus/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoft/regorus/compare/regorus-v0.1.3...regorus-v0.1.4) --- updated-dependencies: - dependency-name: regorus dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 14 ++------------ crates/weaver_checker/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b180c8f9..166d4e77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,9 +114,6 @@ name = "anyhow" version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" -dependencies = [ - "backtrace", -] [[package]] name = "arc-swap" @@ -352,12 +349,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "compact-rc" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf2bdc97c915ed231cf450d1dc4f7293b6135a46834f886f23cfdf8ac2ba4a23" - [[package]] name = "const_format" version = "0.2.32" @@ -2515,14 +2506,13 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "regorus" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f21e15237fe2834687a9063a19f7cf03d4e57188aad587ce855b1c1f723495" +checksum = "ac45cc36ed8e6188529f44deff00c078d9cd0e6cdc831c4dde96c9d6eeb3a46c" dependencies = [ "anyhow", "chrono", "chrono-tz", - "compact-rc", "constant_time_eq", "data-encoding", "hex", diff --git a/crates/weaver_checker/Cargo.toml b/crates/weaver_checker/Cargo.toml index ba7f235e..29b32c49 100644 --- a/crates/weaver_checker/Cargo.toml +++ b/crates/weaver_checker/Cargo.toml @@ -21,7 +21,7 @@ serde.workspace = true serde_json.workspace = true serde_yaml.workspace = true -regorus = { version = "0.1.3", default-features = false, features = [ +regorus = { version = "0.1.4", default-features = false, features = [ "arc", "base64", "base64url", From dc660861e9a85960c60dfdb2ec69813315c7d67d Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Tue, 23 Apr 2024 17:49:40 -0700 Subject: [PATCH 2/6] feat(diagnostic): add diagnostic infrastructure --- Cargo.lock | 159 +++++++++++++--- crates/weaver_common/Cargo.toml | 6 + crates/weaver_common/examples/diag_service.rs | 41 ++++ crates/weaver_common/examples/test.rs | 90 +++++++++ crates/weaver_common/src/diag/channel.rs | 24 +++ .../src/diag/consumer/console.rs | 83 +++++++++ .../weaver_common/src/diag/consumer/json.rs | 3 + crates/weaver_common/src/diag/consumer/mod.rs | 17 ++ crates/weaver_common/src/diag/mod.rs | 175 ++++++++++++++++++ crates/weaver_common/src/lib.rs | 1 + 10 files changed, 578 insertions(+), 21 deletions(-) create mode 100644 crates/weaver_common/examples/diag_service.rs create mode 100644 crates/weaver_common/examples/test.rs create mode 100644 crates/weaver_common/src/diag/channel.rs create mode 100644 crates/weaver_common/src/diag/consumer/console.rs create mode 100644 crates/weaver_common/src/diag/consumer/json.rs create mode 100644 crates/weaver_common/src/diag/consumer/mod.rs create mode 100644 crates/weaver_common/src/diag/mod.rs diff --git a/Cargo.lock b/Cargo.lock index b180c8f9..53b309e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,9 +114,6 @@ name = "anyhow" version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" -dependencies = [ - "backtrace", -] [[package]] name = "arc-swap" @@ -151,6 +148,15 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + [[package]] name = "base64" version = "0.21.7" @@ -352,12 +358,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "compact-rc" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf2bdc97c915ed231cf450d1dc4f7293b6135a46834f886f23cfdf8ac2ba4a23" - [[package]] name = "const_format" version = "0.2.32" @@ -1751,6 +1751,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + [[package]] name = "iso8601" version = "0.6.1" @@ -2009,6 +2015,37 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "374c335b2df19e62d4cb323103473cbc6510980253119180de862d89184f6a83" +[[package]] +name = "miette" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" +dependencies = [ + "backtrace", + "backtrace-ext", + "cfg-if", + "miette-derive", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", + "thiserror", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "mime" version = "0.3.17" @@ -2202,6 +2239,12 @@ dependencies = [ "serde", ] +[[package]] +name = "owo-colors" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" + [[package]] name = "paris" version = "1.5.15" @@ -2515,14 +2558,13 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "regorus" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f21e15237fe2834687a9063a19f7cf03d4e57188aad587ce855b1c1f723495" +checksum = "ac45cc36ed8e6188529f44deff00c078d9cd0e6cdc831c4dde96c9d6eeb3a46c" dependencies = [ "anyhow", "chrono", "chrono-tz", - "compact-rc", "constant_time_eq", "data-encoding", "hex", @@ -2624,9 +2666,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.33" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3cc72858054fcff6d7dea32df2aeaee6a7c24227366d7ea429aada2f26b16ad" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", @@ -2661,15 +2703,15 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" [[package]] name = "rustls-webpki" -version = "0.102.2" +version = "0.102.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" dependencies = [ "ring", "rustls-pki-types", @@ -2870,6 +2912,12 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "socket2" version = "0.5.6" @@ -2904,6 +2952,27 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "supports-color" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9829b314621dfc575df4e409e79f9d6a66a3bd707ab73f23cb4aa3a854ac854f" +dependencies = [ + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0a1e5168041f5f3ff68ff7d95dcb9c8749df29f6e7e89ada40dd4c9de404ee" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + [[package]] name = "syn" version = "2.0.60" @@ -2964,6 +3033,27 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.59" @@ -3017,6 +3107,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -3194,6 +3294,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-normalization" version = "0.1.23" @@ -3209,6 +3315,12 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "unicode-xid" version = "0.2.4" @@ -3434,7 +3546,12 @@ dependencies = [ name = "weaver_common" version = "0.1.0" dependencies = [ + "miette", "paris", + "serde", + "serde_json", + "thiserror", + "tinytemplate", ] [[package]] @@ -3574,11 +3691,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "134306a13c5647ad6453e8deaec55d3a44d6021970129e6188735e74bf546697" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] diff --git a/crates/weaver_common/Cargo.toml b/crates/weaver_common/Cargo.toml index 09563bf2..92c09e91 100644 --- a/crates/weaver_common/Cargo.toml +++ b/crates/weaver_common/Cargo.toml @@ -13,4 +13,10 @@ workspace = true [dependencies] paris = { version = "1.5.15", features = ["macros"] } +tinytemplate = "1.1" +miette = { version = "7.2.0", features = ["fancy"] } +thiserror.workspace = true + +serde.workspace = true +serde_json.workspace = true diff --git a/crates/weaver_common/examples/diag_service.rs b/crates/weaver_common/examples/diag_service.rs new file mode 100644 index 00000000..4e45743a --- /dev/null +++ b/crates/weaver_common/examples/diag_service.rs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! + +// use miette::{Diagnostic, NamedSource, SourceSpan}; +use serde::Serialize; +use weaver_common::diag::channel::DiagChannel; +use weaver_common::diag::consumer::console::ConsoleDiagMessageConsumer; +use weaver_common::diag::{DiagMessage, DiagService}; + +// #[derive(Debug, Diagnostic)] +// #[diagnostic( +// help("try doing it better next time?") +// )] +// struct MyBad { +// test: String, +// } + +fn main() { + // let my_bad = MyBad { test: "test".to_string() }; + // println!("{:?}", my_bad.code()); + // println!("{:?}", my_bad.help()); + + let consumer = ConsoleDiagMessageConsumer::new(true); + let service = DiagService::new(consumer, 10); + let channel = service.channel(); + + app_code(&channel); + + service.stop(); +} + +fn app_code(diag_channel: &DiagChannel) { + #[derive(Serialize)] + struct Test { + field: String, + } + + diag_channel.report(DiagMessage::warn_with_ctx("This is a warning message (field: {field})", Test { field: "value".to_string() })); + diag_channel.report(DiagMessage::error("This is an error message")); +} \ No newline at end of file diff --git a/crates/weaver_common/examples/test.rs b/crates/weaver_common/examples/test.rs new file mode 100644 index 00000000..fc0a439f --- /dev/null +++ b/crates/weaver_common/examples/test.rs @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! + +use std::error::Error; +use miette::{Diagnostic, Report, SourceSpan}; +use miette::{NamedSource, Result}; +use thiserror::Error; + +#[derive(Error, Debug, Diagnostic)] +#[error("oops!")] +#[diagnostic( +code(oops::my::bad), +severity(Error), +url(docsrs), +help("try doing it better next time?") +)] +struct MyDiag { + // The Source that we're gonna be printing snippets out of. + // This can be a String if you don't have or care about file names. + #[source_code] + src: NamedSource, + // Snippets and highlights can be included in the diagnostic! + #[label("This bit here")] + bad_bit: SourceSpan, +} + +/// Todo +pub enum SystemMessage { + /// A diagnostic message. + Diagnostic(Report), + /// A stop message used to stop the diagnostic service. + Stop, +} + +/// Todo +pub struct Channel { + msgs: Vec, +} + +impl Channel { + /// Todo + pub fn report(&mut self, diag: E) { + self.msgs.push(SystemMessage::Diagnostic(diag.into())); + } +} + +fn main() -> Result<()> { + let mut channel = Channel { msgs: Vec::new() }; + + let src = "source\n text\n here".to_string(); + channel.report(MyDiag { + src: NamedSource::new("bad_file.rs", src), + bad_bit: (9, 4).into(), + }); + + let src = "source\n text\n here".to_string(); + channel.report(MyDiag { + src: NamedSource::new("bad_file2.rs", src), + bad_bit: (9, 4).into(), + }); + + for msg in channel.msgs { + match msg { + SystemMessage::Diagnostic(report) => { + println!("{:?}", report); + }, + SystemMessage::Stop => { + println!("Stopping"); + }, + } + } + + Ok(()) +} + +// fn report(diag: E) { +// let report: Report = diag.into(); +// +// let sys_msg = SystemMessage::Diagnostic(report); +// +// match sys_msg { +// SystemMessage::Diagnostic(report) => { +// println!("{:?}", report); +// }, +// SystemMessage::Stop => { +// println!("Stopping"); +// }, +// } +// } \ No newline at end of file diff --git a/crates/weaver_common/src/diag/channel.rs b/crates/weaver_common/src/diag/channel.rs new file mode 100644 index 00000000..158a5938 --- /dev/null +++ b/crates/weaver_common/src/diag/channel.rs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! A channel for reporting diagnostic messages. + +use std::sync::mpsc::SyncSender; +use crate::diag::{DiagMessage, SystemMessage}; + +/// A channel for reporting diagnostic messages. +pub struct DiagChannel { + sender: SyncSender, +} + +impl DiagChannel { + /// Create a new diagnostic channel. + pub(crate) fn new(sender: SyncSender) -> Self { + Self { sender } + } + + /// Report a diagnostic message. + pub fn report(&self, message: DiagMessage) { + self.sender.send(SystemMessage::Diagnostic(message)) + .expect("Failed to send diagnostic message."); + } +} diff --git a/crates/weaver_common/src/diag/consumer/console.rs b/crates/weaver_common/src/diag/consumer/console.rs new file mode 100644 index 00000000..c58f19e1 --- /dev/null +++ b/crates/weaver_common/src/diag/consumer/console.rs @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! A consumer that writes diagnostic messages to the console as plain text. + +use std::sync::mpsc; +use crate::diag::consumer::DiagMessageConsumer; +use crate::diag::{DiagMessage, SystemMessage}; + +/// A consumer that writes diagnostic messages to the console as plain text. +pub struct ConsoleDiagMessageConsumer { + stdout_lock: bool, +} + +impl ConsoleDiagMessageConsumer { + /// Creates a new console consumer. + pub fn new(stdout_lock: bool) -> Self { + Self { + stdout_lock, + } + } +} + +impl DiagMessageConsumer for ConsoleDiagMessageConsumer { + /// Runs the console consumer. + /// The consumer is expected to consume diagnostic messages from the given receiver, report + /// them, and handle the `SystemMessage::Stop` message. + fn run(&self, receiver: mpsc::Receiver, msg_formatter: fn(&DiagMessage) -> String) { + let lock = if self.stdout_lock { + Some(std::io::stdout().lock()) + } else { + None + }; + + for message in receiver { + match message { + SystemMessage::Diagnostic(message) => { + let level = match message.level { + crate::diag::DiagLevel::Warning => "warning", + crate::diag::DiagLevel::Error => "error", + }; + + let location = match message.location { + Some(ref location) => { + let source = match location.source.as_ref() { + Some(source) => format!("{}:", source), + None => "".to_string(), + }; + + let line = match location.line { + Some(line) => format!("{}:", line), + None => "".to_string(), + }; + + let column = match location.column { + Some(column) => format!("{}:", column), + None => "".to_string(), + }; + + format!("{}{}{}", source, line, column) + } + None => "".to_string(), + }; + + let help = match message.help.as_ref() { + Some(help) => format!("help: {}\n", help), + None => "".to_string(), + }; + + let note = match message.note.as_ref() { + Some(note) => format!("note: {}\n", note), + None => "".to_string(), + }; + + println!("{}: {}{}{}{}", level, location, msg_formatter(&message), help, note); + } + SystemMessage::Stop => { + break; + } + } + } + drop(lock); + } +} \ No newline at end of file diff --git a/crates/weaver_common/src/diag/consumer/json.rs b/crates/weaver_common/src/diag/consumer/json.rs new file mode 100644 index 00000000..79f4a37b --- /dev/null +++ b/crates/weaver_common/src/diag/consumer/json.rs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! A consumer that writes diagnostic messages to the standard output as JSON. \ No newline at end of file diff --git a/crates/weaver_common/src/diag/consumer/mod.rs b/crates/weaver_common/src/diag/consumer/mod.rs new file mode 100644 index 00000000..34907a95 --- /dev/null +++ b/crates/weaver_common/src/diag/consumer/mod.rs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! + +pub mod console; +pub mod json; + +use std::sync::mpsc; +use crate::diag::{DiagMessage, SystemMessage}; + +/// A consumer of diagnostic messages. +pub trait DiagMessageConsumer: Send { + /// Runs the consumer. + /// The consumer is expected to consume diagnostic messages from the given receiver, report + /// them, and handle the `SystemMessage::Stop` message. + fn run(&self, receiver: mpsc::Receiver, msg_formatter: fn(&DiagMessage) -> String); +} \ No newline at end of file diff --git a/crates/weaver_common/src/diag/mod.rs b/crates/weaver_common/src/diag/mod.rs new file mode 100644 index 00000000..5cf596d6 --- /dev/null +++ b/crates/weaver_common/src/diag/mod.rs @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! Defines the diagnostic messages and the corresponding infrastructure used by Weaver. + +use std::error::Error; +use std::sync::mpsc::{sync_channel, SyncSender}; +use std::thread; +use miette::Diagnostic; + +use serde::Serialize; +use serde_json::Value; +use tinytemplate::TinyTemplate; + +use crate::diag::channel::DiagChannel; + +pub mod channel; +pub mod consumer; + +/// A generic diagnostic message. +#[derive(Debug, Serialize)] +pub struct DiagMessage { + /// The level of the diagnostic message. + level: DiagLevel, + /// The diagnostic message. Placeholder values can be used. + message: String, + /// The context of the diagnostic message. + context: Value, + /// Help information for the diagnostic message. + help: Option, + /// Additional notes for the diagnostic message. + note: Option, + /// The location of the diagnostic message. + location: Option, +} + +/// The diagnostic level. +#[derive(Debug, Serialize)] +pub enum DiagLevel { + /// A warning message. + Warning, + /// An error message. + Error, +} + +/// The location of a diagnostic message. +#[derive(Debug, Serialize)] +pub struct Location { + source: Option, + line: Option, + column: Option, +} + +/// A system message that can be sent to the diagnostic service. +pub enum SystemMessage { + /// A diagnostic message. + Diagnostic(DiagMessage), + /// A stop message used to stop the diagnostic service. + Stop, +} + +/// A diagnostic service that consumes diagnostic messages and reports them to a consumer. +/// The service runs in a separate thread. +pub struct DiagService { + sender: SyncSender, + join_handle: thread::JoinHandle<()>, +} + +impl DiagMessage { + /// Creates a new diagnostic message with the warning level. + pub fn warn(message: &str) -> Self { + Self { + level: DiagLevel::Warning, + message: message.to_string(), + context: Value::Null, + help: None, + note: None, + location: None, + } + } + + /// Creates a new diagnostic message with the warning level and a context. + pub fn warn_with_ctx(message: &str, ctx: C) -> Self { + Self { + level: DiagLevel::Warning, + message: message.to_string(), + // Todo: Fix this + context: serde_json::to_value(ctx).expect("Failed to serialize context"), + help: None, + note: None, + location: None, + } + } + + /// Creates a new diagnostic message with the error level. + pub fn error(message: &str) -> Self { + Self { + level: DiagLevel::Error, + message: message.to_string(), + context: Value::Null, + help: None, + note: None, + location: None, + } + } +} + +impl DiagService { + /// Creates a new diagnostic service given a [`consumer::DiagMessageConsumer`] and a bound. + /// The bound is the maximum number of messages that can be buffered. + /// If the bound is reached, the sender will block until the buffer is freed. + /// The consumer will consume the messages in the order they were sent. + /// The service will run in a separate thread. + /// + /// The service will stop when the [`DiagService::stop`] method is called. + pub fn new(consumer: impl consumer::DiagMessageConsumer + 'static, bound: usize) -> Self { + let (sender, receiver) = sync_channel(bound); + let join_handle = thread::spawn(move || { + consumer.run(receiver, |msg| { + let mut tt = TinyTemplate::new(); + tt.add_template("formatter", &msg.message).expect("Failed to add template"); + tt.render("formatter", &msg.context).expect("Failed to render message") + }); + }); + + Self { + sender, + join_handle, + } + } + + /// Returns a channel for reporting diagnostic messages. + pub fn channel(&self) -> DiagChannel { + DiagChannel::new(self.sender.clone()) + } + + /// Waits for the diagnostic service to finish. + /// This method should be called at the end of the program. + /// If this method is not called, the program will hang. + /// This method should be called only once. + pub fn stop(self) { + self.sender.send(SystemMessage::Stop).expect("Failed to send stop message."); + self.join_handle.join().expect("Failed to join the diagnostic service thread"); + } +} + +#[cfg(test)] +mod tests { + use crate::diag::consumer::console::ConsoleDiagMessageConsumer; + + use super::*; + + #[test] + fn test_console_diag_service() { + let consumer = ConsoleDiagMessageConsumer::new(true); + let service = DiagService::new(consumer, 10); + let channel = service.channel(); + + app_code(&channel); + + service.stop(); + } + + fn app_code(diag_channel: &DiagChannel) { + let msg = DiagMessage { + level: DiagLevel::Warning, + message: "This is a warning message".to_string(), + context: Value::Null, + help: None, + note: None, + location: None, + }; + diag_channel.report(DiagMessage::warn("This is a warning message")); + diag_channel.report(DiagMessage::error("This is an error message")); + } +} \ No newline at end of file diff --git a/crates/weaver_common/src/lib.rs b/crates/weaver_common/src/lib.rs index cb19d4b0..b33505bb 100644 --- a/crates/weaver_common/src/lib.rs +++ b/crates/weaver_common/src/lib.rs @@ -4,6 +4,7 @@ pub mod error; pub mod quiet; +pub mod diag; use std::sync::atomic::AtomicUsize; use std::sync::{Arc, Mutex}; From 92ac11708fda1ad7c5d83ad79c7d8c4522e1b40b Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Tue, 23 Apr 2024 19:05:21 -0700 Subject: [PATCH 3/6] feat(diagnostic): improve diagnostic infrastructure --- Cargo.lock | 13 - crates/weaver_common/Cargo.toml | 6 +- crates/weaver_common/README.md | 2 +- crates/weaver_common/examples/diag_service.rs | 114 ++++++-- crates/weaver_common/examples/test.rs | 90 ------- crates/weaver_common/src/diag/channel.rs | 12 +- .../src/diag/consumer/console.rs | 69 ++--- .../weaver_common/src/diag/consumer/json.rs | 3 - crates/weaver_common/src/diag/consumer/mod.rs | 11 +- crates/weaver_common/src/diag/mod.rs | 253 +++++++++++------- crates/weaver_common/src/lib.rs | 2 +- 11 files changed, 283 insertions(+), 292 deletions(-) delete mode 100644 crates/weaver_common/examples/test.rs delete mode 100644 crates/weaver_common/src/diag/consumer/json.rs diff --git a/Cargo.lock b/Cargo.lock index 53b309e8..224d6851 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3107,16 +3107,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "tinyvec" version = "1.6.0" @@ -3548,10 +3538,7 @@ version = "0.1.0" dependencies = [ "miette", "paris", - "serde", - "serde_json", "thiserror", - "tinytemplate", ] [[package]] diff --git a/crates/weaver_common/Cargo.toml b/crates/weaver_common/Cargo.toml index 92c09e91..43e3e805 100644 --- a/crates/weaver_common/Cargo.toml +++ b/crates/weaver_common/Cargo.toml @@ -13,10 +13,8 @@ workspace = true [dependencies] paris = { version = "1.5.15", features = ["macros"] } -tinytemplate = "1.1" miette = { version = "7.2.0", features = ["fancy"] } -thiserror.workspace = true -serde.workspace = true -serde_json.workspace = true +[dev-dependencies] +thiserror.workspace = true diff --git a/crates/weaver_common/README.md b/crates/weaver_common/README.md index 0965b168..ceae69d5 100644 --- a/crates/weaver_common/README.md +++ b/crates/weaver_common/README.md @@ -1,4 +1,4 @@ # Weaver Common A set of common tools and cross-cutting functions for the Weaver project such -as error management and generic logging infrastructure. \ No newline at end of file +as error management, generic logging infrastructure, and diagnostic infrastructure. \ No newline at end of file diff --git a/crates/weaver_common/examples/diag_service.rs b/crates/weaver_common/examples/diag_service.rs index 4e45743a..9a7e9a8a 100644 --- a/crates/weaver_common/examples/diag_service.rs +++ b/crates/weaver_common/examples/diag_service.rs @@ -2,25 +2,84 @@ //! -// use miette::{Diagnostic, NamedSource, SourceSpan}; -use serde::Serialize; +use crate::DiagnosticMessages::{MyAdvice, MyError, MyMessage, MyWarning}; +use miette::{Diagnostic, NamedSource, SourceSpan}; +use thiserror::Error; use weaver_common::diag::channel::DiagChannel; use weaver_common::diag::consumer::console::ConsoleDiagMessageConsumer; -use weaver_common::diag::{DiagMessage, DiagService}; +use weaver_common::diag::DiagService; -// #[derive(Debug, Diagnostic)] -// #[diagnostic( -// help("try doing it better next time?") -// )] -// struct MyBad { -// test: String, -// } +#[derive(Error, Diagnostic, Debug)] +enum DiagnosticMessages { + #[error("A fantastic diagnostic error!")] + #[diagnostic( + code(oops::my::bad), + severity(Error), + url(docsrs), + help("try doing it better next time?") + )] + MyError { + // The Source that we're gonna be printing snippets out of. + // This can be a String if you don't have or care about file names. + #[source_code] + src: NamedSource, + // Snippets and highlights can be included in the diagnostic! + #[label("This bit here")] + bad_bit: SourceSpan, + }, -fn main() { - // let my_bad = MyBad { test: "test".to_string() }; - // println!("{:?}", my_bad.code()); - // println!("{:?}", my_bad.help()); + #[error("A fantastic diagnostic advice!")] + #[diagnostic( + code(oops::my::bad), + severity(Advice), + url(docsrs), + help("try doing it better next time?") + )] + MyAdvice { + // The Source that we're gonna be printing snippets out of. + // This can be a String if you don't have or care about file names. + #[source_code] + src: NamedSource, + // Snippets and highlights can be included in the diagnostic! + #[label("This bit here")] + bad_bit: SourceSpan, + }, + + #[error("A fantastic diagnostic warning!")] + #[diagnostic( + code(oops::my::bad), + severity(Warning), + url(docsrs), + help("try doing it better next time?") + )] + MyWarning { + // The Source that we're gonna be printing snippets out of. + // This can be a String if you don't have or care about file names. + #[source_code] + src: NamedSource, + // Snippets and highlights can be included in the diagnostic! + #[label("This bit here")] + bad_bit: SourceSpan, + }, + + #[error("A fantastic diagnostic message!")] + #[diagnostic( + code(oops::my::bad), + url(docsrs), + help("try doing it better next time?") + )] + MyMessage { + // The Source that we're gonna be printing snippets out of. + // This can be a String if you don't have or care about file names. + #[source_code] + src: NamedSource, + // Snippets and highlights can be included in the diagnostic! + #[label("This bit here")] + bad_bit: SourceSpan, + }, +} +fn main() { let consumer = ConsoleDiagMessageConsumer::new(true); let service = DiagService::new(consumer, 10); let channel = service.channel(); @@ -31,11 +90,22 @@ fn main() { } fn app_code(diag_channel: &DiagChannel) { - #[derive(Serialize)] - struct Test { - field: String, - } - - diag_channel.report(DiagMessage::warn_with_ctx("This is a warning message (field: {field})", Test { field: "value".to_string() })); - diag_channel.report(DiagMessage::error("This is an error message")); -} \ No newline at end of file + let src = "source\n text\n here".to_string(); + + diag_channel.report(MyError { + src: NamedSource::new("bad_file.rs", src.clone()), + bad_bit: (9, 4).into(), + }); + diag_channel.report(MyAdvice { + src: NamedSource::new("bad_file.rs", src.clone()), + bad_bit: (9, 4).into(), + }); + diag_channel.report(MyWarning { + src: NamedSource::new("bad_file.rs", src.clone()), + bad_bit: (9, 4).into(), + }); + diag_channel.report(MyMessage { + src: NamedSource::new("bad_file.rs", src), + bad_bit: (9, 4).into(), + }); +} diff --git a/crates/weaver_common/examples/test.rs b/crates/weaver_common/examples/test.rs deleted file mode 100644 index fc0a439f..00000000 --- a/crates/weaver_common/examples/test.rs +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -//! - -use std::error::Error; -use miette::{Diagnostic, Report, SourceSpan}; -use miette::{NamedSource, Result}; -use thiserror::Error; - -#[derive(Error, Debug, Diagnostic)] -#[error("oops!")] -#[diagnostic( -code(oops::my::bad), -severity(Error), -url(docsrs), -help("try doing it better next time?") -)] -struct MyDiag { - // The Source that we're gonna be printing snippets out of. - // This can be a String if you don't have or care about file names. - #[source_code] - src: NamedSource, - // Snippets and highlights can be included in the diagnostic! - #[label("This bit here")] - bad_bit: SourceSpan, -} - -/// Todo -pub enum SystemMessage { - /// A diagnostic message. - Diagnostic(Report), - /// A stop message used to stop the diagnostic service. - Stop, -} - -/// Todo -pub struct Channel { - msgs: Vec, -} - -impl Channel { - /// Todo - pub fn report(&mut self, diag: E) { - self.msgs.push(SystemMessage::Diagnostic(diag.into())); - } -} - -fn main() -> Result<()> { - let mut channel = Channel { msgs: Vec::new() }; - - let src = "source\n text\n here".to_string(); - channel.report(MyDiag { - src: NamedSource::new("bad_file.rs", src), - bad_bit: (9, 4).into(), - }); - - let src = "source\n text\n here".to_string(); - channel.report(MyDiag { - src: NamedSource::new("bad_file2.rs", src), - bad_bit: (9, 4).into(), - }); - - for msg in channel.msgs { - match msg { - SystemMessage::Diagnostic(report) => { - println!("{:?}", report); - }, - SystemMessage::Stop => { - println!("Stopping"); - }, - } - } - - Ok(()) -} - -// fn report(diag: E) { -// let report: Report = diag.into(); -// -// let sys_msg = SystemMessage::Diagnostic(report); -// -// match sys_msg { -// SystemMessage::Diagnostic(report) => { -// println!("{:?}", report); -// }, -// SystemMessage::Stop => { -// println!("Stopping"); -// }, -// } -// } \ No newline at end of file diff --git a/crates/weaver_common/src/diag/channel.rs b/crates/weaver_common/src/diag/channel.rs index 158a5938..819eb779 100644 --- a/crates/weaver_common/src/diag/channel.rs +++ b/crates/weaver_common/src/diag/channel.rs @@ -2,10 +2,13 @@ //! A channel for reporting diagnostic messages. +use crate::diag::SystemMessage; +use miette::Diagnostic; +use std::error::Error; use std::sync::mpsc::SyncSender; -use crate::diag::{DiagMessage, SystemMessage}; /// A channel for reporting diagnostic messages. +#[derive(Clone)] pub struct DiagChannel { sender: SyncSender, } @@ -17,8 +20,9 @@ impl DiagChannel { } /// Report a diagnostic message. - pub fn report(&self, message: DiagMessage) { - self.sender.send(SystemMessage::Diagnostic(message)) - .expect("Failed to send diagnostic message."); + pub fn report(&self, message: M) { + // JUSTIFICATION: The only way this can fail is if the receiver has been dropped. + self.sender.send(SystemMessage::Diagnostic(message.into())) + .expect("The DiagService has been stopped while the application is still running and generating diagnostic messages. Please ensure that the DiagService is stopped only after the rest of the application has finished."); } } diff --git a/crates/weaver_common/src/diag/consumer/console.rs b/crates/weaver_common/src/diag/consumer/console.rs index c58f19e1..10827495 100644 --- a/crates/weaver_common/src/diag/consumer/console.rs +++ b/crates/weaver_common/src/diag/consumer/console.rs @@ -2,9 +2,9 @@ //! A consumer that writes diagnostic messages to the console as plain text. -use std::sync::mpsc; use crate::diag::consumer::DiagMessageConsumer; -use crate::diag::{DiagMessage, SystemMessage}; +use crate::diag::SystemMessage; +use std::sync::mpsc; /// A consumer that writes diagnostic messages to the console as plain text. pub struct ConsoleDiagMessageConsumer { @@ -14,9 +14,7 @@ pub struct ConsoleDiagMessageConsumer { impl ConsoleDiagMessageConsumer { /// Creates a new console consumer. pub fn new(stdout_lock: bool) -> Self { - Self { - stdout_lock, - } + Self { stdout_lock } } } @@ -24,54 +22,33 @@ impl DiagMessageConsumer for ConsoleDiagMessageConsumer { /// Runs the console consumer. /// The consumer is expected to consume diagnostic messages from the given receiver, report /// them, and handle the `SystemMessage::Stop` message. - fn run(&self, receiver: mpsc::Receiver, msg_formatter: fn(&DiagMessage) -> String) { + fn run(&self, receiver: mpsc::Receiver) { + let stdout = std::io::stdout(); let lock = if self.stdout_lock { - Some(std::io::stdout().lock()) + // Used to speed up the output to the console. + Some(stdout.lock()) } else { None }; for message in receiver { match message { - SystemMessage::Diagnostic(message) => { - let level = match message.level { - crate::diag::DiagLevel::Warning => "warning", - crate::diag::DiagLevel::Error => "error", - }; - - let location = match message.location { - Some(ref location) => { - let source = match location.source.as_ref() { - Some(source) => format!("{}:", source), - None => "".to_string(), - }; - - let line = match location.line { - Some(line) => format!("{}:", line), - None => "".to_string(), - }; - - let column = match location.column { - Some(column) => format!("{}:", column), - None => "".to_string(), - }; - - format!("{}{}{}", source, line, column) + SystemMessage::Diagnostic(report) => { + if let Some(severity) = report.severity() { + match severity { + miette::Severity::Advice => { + println!("Advice: {:?}", report); + } + miette::Severity::Warning => { + eprintln!("Warning: {:?}", report); + } + miette::Severity::Error => { + eprintln!("Error: {:?}", report); + } } - None => "".to_string(), - }; - - let help = match message.help.as_ref() { - Some(help) => format!("help: {}\n", help), - None => "".to_string(), - }; - - let note = match message.note.as_ref() { - Some(note) => format!("note: {}\n", note), - None => "".to_string(), - }; - - println!("{}: {}{}{}{}", level, location, msg_formatter(&message), help, note); + } else { + println!("{:?}", report); + } } SystemMessage::Stop => { break; @@ -80,4 +57,4 @@ impl DiagMessageConsumer for ConsoleDiagMessageConsumer { } drop(lock); } -} \ No newline at end of file +} diff --git a/crates/weaver_common/src/diag/consumer/json.rs b/crates/weaver_common/src/diag/consumer/json.rs deleted file mode 100644 index 79f4a37b..00000000 --- a/crates/weaver_common/src/diag/consumer/json.rs +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -//! A consumer that writes diagnostic messages to the standard output as JSON. \ No newline at end of file diff --git a/crates/weaver_common/src/diag/consumer/mod.rs b/crates/weaver_common/src/diag/consumer/mod.rs index 34907a95..21eec523 100644 --- a/crates/weaver_common/src/diag/consumer/mod.rs +++ b/crates/weaver_common/src/diag/consumer/mod.rs @@ -1,17 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 -//! +//! A consumer that consumes diagnostic messages. pub mod console; -pub mod json; +use crate::diag::SystemMessage; use std::sync::mpsc; -use crate::diag::{DiagMessage, SystemMessage}; /// A consumer of diagnostic messages. pub trait DiagMessageConsumer: Send { /// Runs the consumer. /// The consumer is expected to consume diagnostic messages from the given receiver, report - /// them, and handle the `SystemMessage::Stop` message. - fn run(&self, receiver: mpsc::Receiver, msg_formatter: fn(&DiagMessage) -> String); -} \ No newline at end of file + /// them, and stop when the `SystemMessage::Stop` message is received. + fn run(&self, receiver: mpsc::Receiver); +} diff --git a/crates/weaver_common/src/diag/mod.rs b/crates/weaver_common/src/diag/mod.rs index 5cf596d6..56c5357d 100644 --- a/crates/weaver_common/src/diag/mod.rs +++ b/crates/weaver_common/src/diag/mod.rs @@ -1,59 +1,23 @@ // SPDX-License-Identifier: Apache-2.0 -//! Defines the diagnostic messages and the corresponding infrastructure used by Weaver. +//! Diagnostic infrastructure used to report diagnostic messages to the user. +//! The diagnostic messages are based on the [`miette`] crate. +//! This infrastructure is designed to be extensible and flexible. -use std::error::Error; use std::sync::mpsc::{sync_channel, SyncSender}; use std::thread; -use miette::Diagnostic; -use serde::Serialize; -use serde_json::Value; -use tinytemplate::TinyTemplate; +use miette::Report; use crate::diag::channel::DiagChannel; pub mod channel; pub mod consumer; -/// A generic diagnostic message. -#[derive(Debug, Serialize)] -pub struct DiagMessage { - /// The level of the diagnostic message. - level: DiagLevel, - /// The diagnostic message. Placeholder values can be used. - message: String, - /// The context of the diagnostic message. - context: Value, - /// Help information for the diagnostic message. - help: Option, - /// Additional notes for the diagnostic message. - note: Option, - /// The location of the diagnostic message. - location: Option, -} - -/// The diagnostic level. -#[derive(Debug, Serialize)] -pub enum DiagLevel { - /// A warning message. - Warning, - /// An error message. - Error, -} - -/// The location of a diagnostic message. -#[derive(Debug, Serialize)] -pub struct Location { - source: Option, - line: Option, - column: Option, -} - /// A system message that can be sent to the diagnostic service. pub enum SystemMessage { - /// A diagnostic message. - Diagnostic(DiagMessage), + /// A diagnostic report. + Diagnostic(Report), /// A stop message used to stop the diagnostic service. Stop, } @@ -65,45 +29,6 @@ pub struct DiagService { join_handle: thread::JoinHandle<()>, } -impl DiagMessage { - /// Creates a new diagnostic message with the warning level. - pub fn warn(message: &str) -> Self { - Self { - level: DiagLevel::Warning, - message: message.to_string(), - context: Value::Null, - help: None, - note: None, - location: None, - } - } - - /// Creates a new diagnostic message with the warning level and a context. - pub fn warn_with_ctx(message: &str, ctx: C) -> Self { - Self { - level: DiagLevel::Warning, - message: message.to_string(), - // Todo: Fix this - context: serde_json::to_value(ctx).expect("Failed to serialize context"), - help: None, - note: None, - location: None, - } - } - - /// Creates a new diagnostic message with the error level. - pub fn error(message: &str) -> Self { - Self { - level: DiagLevel::Error, - message: message.to_string(), - context: Value::Null, - help: None, - note: None, - location: None, - } - } -} - impl DiagService { /// Creates a new diagnostic service given a [`consumer::DiagMessageConsumer`] and a bound. /// The bound is the maximum number of messages that can be buffered. @@ -115,11 +40,7 @@ impl DiagService { pub fn new(consumer: impl consumer::DiagMessageConsumer + 'static, bound: usize) -> Self { let (sender, receiver) = sync_channel(bound); let join_handle = thread::spawn(move || { - consumer.run(receiver, |msg| { - let mut tt = TinyTemplate::new(); - tt.add_template("formatter", &msg.message).expect("Failed to add template"); - tt.render("formatter", &msg.context).expect("Failed to render message") - }); + consumer.run(receiver); }); Self { @@ -128,7 +49,7 @@ impl DiagService { } } - /// Returns a channel for reporting diagnostic messages. + /// Returns a channel for reporting diagnostic reports. pub fn channel(&self) -> DiagChannel { DiagChannel::new(self.sender.clone()) } @@ -138,38 +59,166 @@ impl DiagService { /// If this method is not called, the program will hang. /// This method should be called only once. pub fn stop(self) { - self.sender.send(SystemMessage::Stop).expect("Failed to send stop message."); - self.join_handle.join().expect("Failed to join the diagnostic service thread"); + self.sender + .send(SystemMessage::Stop) + .expect("The DiagService has already been stopped."); + self.join_handle + .join() + .expect("The DiagService thread has panicked."); } } #[cfg(test)] mod tests { + use miette::{Diagnostic, NamedSource, SourceSpan}; + use thiserror::Error; + use crate::diag::consumer::console::ConsoleDiagMessageConsumer; use super::*; + #[derive(Error, Diagnostic, Debug)] + enum DiagMessages { + #[error("A fantastic diagnostic error!")] + #[diagnostic( + code(oops::my::bad), + severity(Error), + url(docsrs), + help("try doing it better next time?") + )] + MyError { + // The Source that we're gonna be printing snippets out of. + // This can be a String if you don't have or care about file names. + #[source_code] + src: NamedSource, + // Snippets and highlights can be included in the diagnostic! + #[label("This bit here")] + bad_bit: SourceSpan, + }, + + #[error("A fantastic diagnostic advice!")] + #[diagnostic( + code(oops::my::bad), + severity(Advice), + url(docsrs), + help("try doing it better next time?") + )] + MyAdvice { + // The Source that we're gonna be printing snippets out of. + // This can be a String if you don't have or care about file names. + #[source_code] + src: NamedSource, + // Snippets and highlights can be included in the diagnostic! + #[label("This bit here")] + bad_bit: SourceSpan, + }, + + #[error("A fantastic diagnostic warning!")] + #[diagnostic( + code(oops::my::bad), + severity(Warning), + url(docsrs), + help("try doing it better next time?") + )] + MyWarning { + // The Source that we're gonna be printing snippets out of. + // This can be a String if you don't have or care about file names. + #[source_code] + src: NamedSource, + // Snippets and highlights can be included in the diagnostic! + #[label("This bit here")] + bad_bit: SourceSpan, + }, + + #[error("A fantastic diagnostic message!")] + #[diagnostic( + code(oops::my::bad), + url(docsrs), + help("try doing it better next time?") + )] + MyMessage { + // The Source that we're gonna be printing snippets out of. + // This can be a String if you don't have or care about file names. + #[source_code] + src: NamedSource, + // Snippets and highlights can be included in the diagnostic! + #[label("This bit here")] + bad_bit: SourceSpan, + }, + } + #[test] fn test_console_diag_service() { let consumer = ConsoleDiagMessageConsumer::new(true); let service = DiagService::new(consumer, 10); let channel = service.channel(); - app_code(&channel); + single_thread_app(&channel); + multi_thread_app(&channel); service.stop(); } - fn app_code(diag_channel: &DiagChannel) { - let msg = DiagMessage { - level: DiagLevel::Warning, - message: "This is a warning message".to_string(), - context: Value::Null, - help: None, - note: None, - location: None, - }; - diag_channel.report(DiagMessage::warn("This is a warning message")); - diag_channel.report(DiagMessage::error("This is an error message")); + #[test] + fn test_console_diag_service_without_stdout_lock() { + let consumer = ConsoleDiagMessageConsumer::new(false); + let service = DiagService::new(consumer, 10); + let channel = service.channel(); + + single_thread_app(&channel); + multi_thread_app(&channel); + + service.stop(); + } + + /// This function represent a single threaded application that reports a diagnostic message. + fn single_thread_app(diag_channel: &DiagChannel) { + let src = "source\n text\n here".to_string(); + + diag_channel.report(DiagMessages::MyError { + src: NamedSource::new("bad_file.rs", src.clone()), + bad_bit: (9, 4).into(), + }); + diag_channel.report(DiagMessages::MyAdvice { + src: NamedSource::new("bad_file.rs", src.clone()), + bad_bit: (9, 4).into(), + }); + diag_channel.report(DiagMessages::MyWarning { + src: NamedSource::new("bad_file.rs", src.clone()), + bad_bit: (9, 4).into(), + }); + diag_channel.report(DiagMessages::MyMessage { + src: NamedSource::new("bad_file.rs", src), + bad_bit: (9, 4).into(), + }); } -} \ No newline at end of file + + /// This function represent a multithreaded application that reports a diagnostic message. + /// Note that the Rust compiler will force you to clone the `DiagChannel` to pass it to the + /// thread. + fn multi_thread_app(diag_channel: &DiagChannel) { + let diag_channel = diag_channel.clone(); + + _ = thread::spawn(move || { + let src = "source\n text\n here".to_string(); + + diag_channel.report(DiagMessages::MyError { + src: NamedSource::new("bad_file.rs", src.clone()), + bad_bit: (9, 4).into(), + }); + diag_channel.report(DiagMessages::MyAdvice { + src: NamedSource::new("bad_file.rs", src.clone()), + bad_bit: (9, 4).into(), + }); + diag_channel.report(DiagMessages::MyWarning { + src: NamedSource::new("bad_file.rs", src.clone()), + bad_bit: (9, 4).into(), + }); + diag_channel.report(DiagMessages::MyMessage { + src: NamedSource::new("bad_file.rs", src), + bad_bit: (9, 4).into(), + }); + }) + .join(); + } +} diff --git a/crates/weaver_common/src/lib.rs b/crates/weaver_common/src/lib.rs index b33505bb..796c639f 100644 --- a/crates/weaver_common/src/lib.rs +++ b/crates/weaver_common/src/lib.rs @@ -2,9 +2,9 @@ #![doc = include_str!("../README.md")] +pub mod diag; pub mod error; pub mod quiet; -pub mod diag; use std::sync::atomic::AtomicUsize; use std::sync::{Arc, Mutex}; From ca69fbe0a72dbcf446f8857e6813fd77014a3481 Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Tue, 23 Apr 2024 19:19:22 -0700 Subject: [PATCH 4/6] feat(diagnostic): fix clippy issues --- .../weaver_common/allowed-external-types.toml | 2 ++ crates/weaver_common/examples/diag_service.rs | 18 ++++++------ .../src/diag/consumer/console.rs | 4 +++ crates/weaver_common/src/diag/mod.rs | 29 ++++++++++--------- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/crates/weaver_common/allowed-external-types.toml b/crates/weaver_common/allowed-external-types.toml index f186b677..34aa778b 100644 --- a/crates/weaver_common/allowed-external-types.toml +++ b/crates/weaver_common/allowed-external-types.toml @@ -3,4 +3,6 @@ # This is used with cargo-check-external-types to reduce the surface area of downstream crates from # the public API. Ideally this can have a few exceptions as possible. allowed_external_types = [ + "miette::protocol::Diagnostic", + "miette::eyreish::Report", ] \ No newline at end of file diff --git a/crates/weaver_common/examples/diag_service.rs b/crates/weaver_common/examples/diag_service.rs index 9a7e9a8a..83c22788 100644 --- a/crates/weaver_common/examples/diag_service.rs +++ b/crates/weaver_common/examples/diag_service.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 -//! +//! An example that demonstrates how to use the diagnostic service. -use crate::DiagnosticMessages::{MyAdvice, MyError, MyMessage, MyWarning}; +use crate::DiagnosticMessages::{AnAdvice, Message, MyError, TheWarning}; use miette::{Diagnostic, NamedSource, SourceSpan}; use thiserror::Error; use weaver_common::diag::channel::DiagChannel; @@ -35,7 +35,7 @@ enum DiagnosticMessages { url(docsrs), help("try doing it better next time?") )] - MyAdvice { + AnAdvice { // The Source that we're gonna be printing snippets out of. // This can be a String if you don't have or care about file names. #[source_code] @@ -52,7 +52,7 @@ enum DiagnosticMessages { url(docsrs), help("try doing it better next time?") )] - MyWarning { + TheWarning { // The Source that we're gonna be printing snippets out of. // This can be a String if you don't have or care about file names. #[source_code] @@ -68,7 +68,7 @@ enum DiagnosticMessages { url(docsrs), help("try doing it better next time?") )] - MyMessage { + Message { // The Source that we're gonna be printing snippets out of. // This can be a String if you don't have or care about file names. #[source_code] @@ -90,21 +90,21 @@ fn main() { } fn app_code(diag_channel: &DiagChannel) { - let src = "source\n text\n here".to_string(); + let src = "source\n text\n here".to_owned(); diag_channel.report(MyError { src: NamedSource::new("bad_file.rs", src.clone()), bad_bit: (9, 4).into(), }); - diag_channel.report(MyAdvice { + diag_channel.report(AnAdvice { src: NamedSource::new("bad_file.rs", src.clone()), bad_bit: (9, 4).into(), }); - diag_channel.report(MyWarning { + diag_channel.report(TheWarning { src: NamedSource::new("bad_file.rs", src.clone()), bad_bit: (9, 4).into(), }); - diag_channel.report(MyMessage { + diag_channel.report(Message { src: NamedSource::new("bad_file.rs", src), bad_bit: (9, 4).into(), }); diff --git a/crates/weaver_common/src/diag/consumer/console.rs b/crates/weaver_common/src/diag/consumer/console.rs index 10827495..341cfa49 100644 --- a/crates/weaver_common/src/diag/consumer/console.rs +++ b/crates/weaver_common/src/diag/consumer/console.rs @@ -2,6 +2,9 @@ //! A consumer that writes diagnostic messages to the console as plain text. +#![allow(clippy::print_stdout)] +#![allow(clippy::print_stderr)] + use crate::diag::consumer::DiagMessageConsumer; use crate::diag::SystemMessage; use std::sync::mpsc; @@ -13,6 +16,7 @@ pub struct ConsoleDiagMessageConsumer { impl ConsoleDiagMessageConsumer { /// Creates a new console consumer. + #[must_use] pub fn new(stdout_lock: bool) -> Self { Self { stdout_lock } } diff --git a/crates/weaver_common/src/diag/mod.rs b/crates/weaver_common/src/diag/mod.rs index 56c5357d..a86dcc2a 100644 --- a/crates/weaver_common/src/diag/mod.rs +++ b/crates/weaver_common/src/diag/mod.rs @@ -50,6 +50,7 @@ impl DiagService { } /// Returns a channel for reporting diagnostic reports. + #[must_use] pub fn channel(&self) -> DiagChannel { DiagChannel::new(self.sender.clone()) } @@ -86,7 +87,7 @@ mod tests { url(docsrs), help("try doing it better next time?") )] - MyError { + Error { // The Source that we're gonna be printing snippets out of. // This can be a String if you don't have or care about file names. #[source_code] @@ -103,7 +104,7 @@ mod tests { url(docsrs), help("try doing it better next time?") )] - MyAdvice { + Advice { // The Source that we're gonna be printing snippets out of. // This can be a String if you don't have or care about file names. #[source_code] @@ -120,7 +121,7 @@ mod tests { url(docsrs), help("try doing it better next time?") )] - MyWarning { + Warning { // The Source that we're gonna be printing snippets out of. // This can be a String if you don't have or care about file names. #[source_code] @@ -136,7 +137,7 @@ mod tests { url(docsrs), help("try doing it better next time?") )] - MyMessage { + Message { // The Source that we're gonna be printing snippets out of. // This can be a String if you don't have or care about file names. #[source_code] @@ -173,21 +174,21 @@ mod tests { /// This function represent a single threaded application that reports a diagnostic message. fn single_thread_app(diag_channel: &DiagChannel) { - let src = "source\n text\n here".to_string(); + let src = "source\n text\n here".to_owned(); - diag_channel.report(DiagMessages::MyError { + diag_channel.report(DiagMessages::Error { src: NamedSource::new("bad_file.rs", src.clone()), bad_bit: (9, 4).into(), }); - diag_channel.report(DiagMessages::MyAdvice { + diag_channel.report(DiagMessages::Advice { src: NamedSource::new("bad_file.rs", src.clone()), bad_bit: (9, 4).into(), }); - diag_channel.report(DiagMessages::MyWarning { + diag_channel.report(DiagMessages::Warning { src: NamedSource::new("bad_file.rs", src.clone()), bad_bit: (9, 4).into(), }); - diag_channel.report(DiagMessages::MyMessage { + diag_channel.report(DiagMessages::Message { src: NamedSource::new("bad_file.rs", src), bad_bit: (9, 4).into(), }); @@ -200,21 +201,21 @@ mod tests { let diag_channel = diag_channel.clone(); _ = thread::spawn(move || { - let src = "source\n text\n here".to_string(); + let src = "source\n text\n here".to_owned(); - diag_channel.report(DiagMessages::MyError { + diag_channel.report(DiagMessages::Error { src: NamedSource::new("bad_file.rs", src.clone()), bad_bit: (9, 4).into(), }); - diag_channel.report(DiagMessages::MyAdvice { + diag_channel.report(DiagMessages::Advice { src: NamedSource::new("bad_file.rs", src.clone()), bad_bit: (9, 4).into(), }); - diag_channel.report(DiagMessages::MyWarning { + diag_channel.report(DiagMessages::Warning { src: NamedSource::new("bad_file.rs", src.clone()), bad_bit: (9, 4).into(), }); - diag_channel.report(DiagMessages::MyMessage { + diag_channel.report(DiagMessages::Message { src: NamedSource::new("bad_file.rs", src), bad_bit: (9, 4).into(), }); From d79b3390ec0504fdf204b0ffa480ca630bc1ffc3 Mon Sep 17 00:00:00 2001 From: Laurent Querel Date: Tue, 23 Apr 2024 23:52:12 -0700 Subject: [PATCH 5/6] feat(diagnostic): create a lock on both stdout and stderr --- .../weaver_common/src/diag/consumer/console.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/weaver_common/src/diag/consumer/console.rs b/crates/weaver_common/src/diag/consumer/console.rs index 341cfa49..599fb198 100644 --- a/crates/weaver_common/src/diag/consumer/console.rs +++ b/crates/weaver_common/src/diag/consumer/console.rs @@ -11,14 +11,19 @@ use std::sync::mpsc; /// A consumer that writes diagnostic messages to the console as plain text. pub struct ConsoleDiagMessageConsumer { - stdout_lock: bool, + enable_output_locking: bool, } impl ConsoleDiagMessageConsumer { /// Creates a new console consumer. + /// + /// If `enable_output_locking` is true, the output (stdout and stderr) will be locked to speed + /// up the output to the console. #[must_use] - pub fn new(stdout_lock: bool) -> Self { - Self { stdout_lock } + pub fn new(enable_output_locking: bool) -> Self { + Self { + enable_output_locking, + } } } @@ -28,9 +33,10 @@ impl DiagMessageConsumer for ConsoleDiagMessageConsumer { /// them, and handle the `SystemMessage::Stop` message. fn run(&self, receiver: mpsc::Receiver) { let stdout = std::io::stdout(); - let lock = if self.stdout_lock { + let stderr = std::io::stderr(); + let lock = if self.enable_output_locking { // Used to speed up the output to the console. - Some(stdout.lock()) + Some((stdout.lock(), stderr.lock())) } else { None }; From 0e5048880128ac1e02ed949d3255f1eb17b0f998 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 24 Apr 2024 07:51:03 -0400 Subject: [PATCH 6/6] (fix) Use id instead of value in enum table --- crates/weaver_semconv_gen/src/gen.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/weaver_semconv_gen/src/gen.rs b/crates/weaver_semconv_gen/src/gen.rs index 294a5429..d94b1ff4 100644 --- a/crates/weaver_semconv_gen/src/gen.rs +++ b/crates/weaver_semconv_gen/src/gen.rs @@ -198,8 +198,8 @@ impl<'a> AttributeView<'a> { if let Some(v) = m.brief.as_ref() { write!(out, "{}", v.trim())?; } else { - // Use the value as the description if missing a brief. - write!(out, "{}", m.value)?; + // Use the id as the description if missing a brief. + write!(out, "{}", m.id)?; } // Stability. write!(out, " | ")?;