From 671fb8384bf2970012a4c191959dee25b9a9405b Mon Sep 17 00:00:00 2001 From: Schneems Date: Fri, 26 Jan 2024 15:10:49 -0600 Subject: [PATCH] s/log/output for buildpack_output Uses consistent naming so that we distinguish between the general concept of a "log" which is not usually visible to the end user and the "output" which is the primary interface with our end user. Leaves the `section_log` code and renames it `inline_output` --- README.md | 2 +- .../src/output/buildpack_output.rs | 86 +++++++++--------- .../src/output/inline_output.rs | 84 ++++++++++++++++++ libherokubuildpack/src/output/mod.rs | 1 + libherokubuildpack/src/output/section_log.rs | 87 ------------------- 5 files changed, 126 insertions(+), 134 deletions(-) create mode 100644 libherokubuildpack/src/output/inline_output.rs delete mode 100644 libherokubuildpack/src/output/section_log.rs diff --git a/README.md b/README.md index 67e788b5..098bda5a 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ That's all we need! We can now move on to finally write some buildpack code! ### Writing the Buildpack -The buildpack we're writing will be very simple. We will just log a "Hello World" message during the build +The buildpack we're writing will be very simple. We will just output a "Hello World" message during the build and set the default process type to a command that will also emit "Hello World" when the application image is run. Examples of more complex buildpacks can be found in the [examples directory](https://github.com/heroku/libcnb.rs/tree/main/examples). diff --git a/libherokubuildpack/src/output/buildpack_output.rs b/libherokubuildpack/src/output/buildpack_output.rs index 14ee297a..987dcb1b 100644 --- a/libherokubuildpack/src/output/buildpack_output.rs +++ b/libherokubuildpack/src/output/buildpack_output.rs @@ -1,6 +1,6 @@ -//! # Build output logging +//! # Buildpack output //! -//! Use the `BuildLog` to output structured text as a buildpack is executing +//! Use the [`BuildpackOutput`] to output structured text as a buildpack is executing //! //! ``` //! use libherokubuildpack::output::buildpack_output::BuildpackOutput; @@ -15,9 +15,7 @@ //! output.finish(); //! ``` //! -//! To log inside of a layer see `section_log`. -//! -//! For usage details run `cargo run --bin print_style_guide` +//! To output inside of a layer see [`inline_output`]. use crate::output::style; use std::fmt::Debug; use std::io::Write; @@ -33,7 +31,7 @@ pub struct BuildpackOutput { pub(crate) _state: T, } -/// A bag of data passed throughout the lifecycle of a `BuildLog` +/// A bag of data passed throughout the lifecycle of a [`BuildpackOutput`] #[derive(Debug)] pub(crate) struct BuildData { pub(crate) started: Instant, @@ -47,9 +45,9 @@ impl Default for BuildData { } } -/// Various states for `BuildOutput` to contain +/// Various states for [`BuildpackOutput`] to contain /// -/// The `BuildLog` struct acts as a logging state machine. These structs +/// The [`BuildpackOutput`] struct acts as an output state machine. These structs /// are meant to represent those states pub(crate) mod state { #[derive(Debug)] @@ -114,8 +112,8 @@ where self.io } - pub fn announce(self) -> AnnounceLog { - AnnounceLog { + pub fn announce(self) -> Announce { + Announce { io: self.io, data: self.data, _state: state::Started, @@ -139,12 +137,12 @@ where self } - pub fn step_timed_stream(mut self, s: &str) -> StreamLog { + pub fn step_timed_stream(mut self, s: &str) -> Stream { self.mut_step(s); let started = Instant::now(); let arc_io = Arc::new(Mutex::new(self.io)); - let mut stream = StreamLog { + let mut stream = Stream { arc_io, data: self.data, started, @@ -162,8 +160,8 @@ where } } - pub fn announce(self) -> AnnounceLog { - AnnounceLog { + pub fn announce(self) -> Announce { + Announce { io: self.io, data: self.data, _state: state::Section, @@ -174,7 +172,7 @@ where // Store internal state, print leading character exactly once on warning or important #[derive(Debug)] -pub struct AnnounceLog +pub struct Announce where W: Write + Send + Sync + Debug + 'static, { @@ -184,12 +182,12 @@ where leader: Option, } -impl AnnounceLog +impl Announce where T: Debug, W: Write + Send + Sync + Debug + 'static, { - fn log_warning(&mut self, s: &str) { + fn shared_warning(&mut self, s: &str) { if let Some(leader) = self.leader.take() { write_now(&mut self.io, leader); } @@ -198,7 +196,7 @@ where writeln_now(&mut self.io, ""); } - fn log_important(&mut self, s: &str) { + fn shared_important(&mut self, s: &str) { if let Some(leader) = self.leader.take() { write_now(&mut self.io, leader); } @@ -215,20 +213,20 @@ where } } -impl AnnounceLog +impl Announce where W: Write + Send + Sync + Debug + 'static, { #[must_use] - pub fn warning(mut self, s: &str) -> AnnounceLog { - self.log_warning(s); + pub fn warning(mut self, s: &str) -> Announce { + self.shared_warning(s); self } #[must_use] - pub fn important(mut self, s: &str) -> AnnounceLog { - self.log_important(s); + pub fn important(mut self, s: &str) -> Announce { + self.shared_important(s); self } @@ -242,19 +240,19 @@ where } } -impl AnnounceLog +impl Announce where W: Write + Send + Sync + Debug + 'static, { #[must_use] - pub fn warning(mut self, s: &str) -> AnnounceLog { - self.log_warning(s); + pub fn warning(mut self, s: &str) -> Announce { + self.shared_warning(s); self } #[must_use] - pub fn important(mut self, s: &str) -> AnnounceLog { - self.log_important(s); + pub fn important(mut self, s: &str) -> Announce { + self.shared_important(s); self } @@ -279,39 +277,39 @@ where W: Write + Send + Sync + Debug + 'static, { fn write(&mut self, buf: &[u8]) -> std::io::Result { - let mut io = self.arc.lock().expect("Logging mutex poisoned"); + let mut io = self.arc.lock().expect("Output mutex poisoned"); io.write(buf) } fn flush(&mut self) -> std::io::Result<()> { - let mut io = self.arc.lock().expect("Logging mutex poisoned"); + let mut io = self.arc.lock().expect("Output mutex poisoned"); io.flush() } } /// Stream output to the user /// -/// Mostly used for logging a running command +/// Mostly used for ouputting a running command #[derive(Debug)] -pub struct StreamLog { +pub struct Stream { data: BuildData, arc_io: Arc>, started: Instant, } -impl StreamLog +impl Stream where W: Write + Send + Sync + Debug + 'static, { fn start(&mut self) { - let mut guard = self.arc_io.lock().expect("Logging mutex poisoned"); + let mut guard = self.arc_io.lock().expect("Output mutex poisoned"); let mut io = guard.by_ref(); // Newline before stream https://github.com/heroku/libcnb.rs/issues/582 writeln_now(&mut io, ""); } /// Yield boxed writer that can be used for formatting and streaming contents - /// back to the logger. + /// back to the output. pub fn io(&mut self) -> Box { Box::new(crate::write::line_mapped( LockedWriter { @@ -338,7 +336,7 @@ where let mut io = Arc::try_unwrap(self.arc_io) .expect("Expected buildpack author to not retain any IO streaming IO instances") .into_inner() - .expect("Logging mutex was poisioned"); + .expect("Output mutex was poisioned"); // // Newline after stream writeln_now(&mut io, ""); @@ -362,20 +360,16 @@ where /// /// This is especially important for writing individual characters to the same line fn write_now(destination: &mut D, msg: impl AsRef) { - write!(destination, "{}", msg.as_ref()).expect("Logging error: UI writer closed"); + write!(destination, "{}", msg.as_ref()).expect("Output error: UI writer closed"); - destination - .flush() - .expect("Logging error: UI writer closed"); + destination.flush().expect("Output error: UI writer closed"); } /// Internal helper, ensures that all contents are always flushed (never buffered) fn writeln_now(destination: &mut D, msg: impl AsRef) { - writeln!(destination, "{}", msg.as_ref()).expect("Logging error: UI writer closed"); + writeln!(destination, "{}", msg.as_ref()).expect("Output error: UI writer closed"); - destination - .flush() - .expect("Logging error: UI writer closed"); + destination.flush().expect("Output error: UI writer closed"); } #[cfg(test)] @@ -479,13 +473,13 @@ mod test { #[test] fn double_warning_step_padding() { let writer = Vec::new(); - let logger = BuildpackOutput::new(writer) + let output = BuildpackOutput::new(writer) .start("RCT") .section("Guest thoughts") .step("The scenery here is wonderful") .announce(); - let io = logger + let io = output .warning("It's too crowded here") .warning("I'm tired") .end_announce() diff --git a/libherokubuildpack/src/output/inline_output.rs b/libherokubuildpack/src/output/inline_output.rs new file mode 100644 index 00000000..b2f12e67 --- /dev/null +++ b/libherokubuildpack/src/output/inline_output.rs @@ -0,0 +1,84 @@ +//! Write to the build output in a [`BuildpackOutput`] format with functions +//! +//! ## What +//! +//! Outputting from within a layer can be difficult because calls to the layer interface are not +//! mutable nor consumable. Functions can be used at any time with no restrictions. The +//! only downside is that the buildpack author (you) is now responsible for: +//! +//! - Ensuring that [`BuildpackOutput::section`] funciton was called right before any of these +//! functions are called. +//! - Ensuring that you are not attempting to output while already streaming i.e. calling [`step`] within +//! a [`step_stream`] call. +//! +//! ## Use +//! +//! The main use case is writing output in a layer: +//! +//! ```no_run +//! use libherokubuildpack::output::inline_output; +//! +//! inline_output::step("Clearing the cache") +//! ``` +use crate::output::buildpack_output::{state, BuildData, BuildpackOutput, Stream}; +use std::io::Stdout; + +/// Output a message as a single step, ideally a short message +/// +/// ``` +/// use libherokubuildpack::output::inline_output; +/// +/// inline_output::step("Clearing cache (ruby version changed)"); +/// ``` +pub fn step(s: impl AsRef) { + let _ = build_buildpack_output().step(s.as_ref()); +} + +/// Will print the input string and yield a [`Stream`] that can be used to print +/// to the output. The main use case is running commands +/// +/// ```no_run +/// use fun_run::CommandWithName; +/// use libherokubuildpack::output::inline_output; +/// use libherokubuildpack::output::style; +/// +/// let mut cmd = std::process::Command::new("bundle"); +/// cmd.arg("install"); +/// +/// inline_output::step_stream(format!("Running {}", style::command(cmd.name())), |stream| { +/// cmd.stream_output(stream.io(), stream.io()).unwrap() +/// }); +/// ``` +/// +/// Timing information will be output at the end of the step. +pub fn step_stream(s: impl AsRef, f: impl FnOnce(&mut Stream) -> T) -> T { + let mut stream = build_buildpack_output().step_timed_stream(s.as_ref()); + let out = f(&mut stream); + let _ = stream.finish_timed_stream(); + out +} + +/// Print an error block to the output +pub fn error(s: impl AsRef) { + build_buildpack_output().announce().error(s.as_ref()); +} + +/// Print an warning block to the output +pub fn warning(s: impl AsRef) { + let _ = build_buildpack_output().announce().warning(s.as_ref()); +} + +/// Print an important block to the output +pub fn important(s: impl AsRef) { + let _ = build_buildpack_output().announce().important(s.as_ref()); +} + +fn build_buildpack_output() -> BuildpackOutput { + BuildpackOutput:: { + io: std::io::stdout(), + // Be careful not to do anything that might access this state + // as it's ephemeral data (i.e. not passed in from the start of the build) + data: BuildData::default(), + _state: state::Section, + } +} diff --git a/libherokubuildpack/src/output/mod.rs b/libherokubuildpack/src/output/mod.rs index c175f985..4ed0c4af 100644 --- a/libherokubuildpack/src/output/mod.rs +++ b/libherokubuildpack/src/output/mod.rs @@ -1,3 +1,4 @@ pub mod buildpack_output; +pub mod inline_output; pub mod style; mod util; diff --git a/libherokubuildpack/src/output/section_log.rs b/libherokubuildpack/src/output/section_log.rs deleted file mode 100644 index a83ae406..00000000 --- a/libherokubuildpack/src/output/section_log.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! Write to the build output in a `Box` format with functions -//! -//! ## What -//! -//! Logging from within a layer can be difficult because calls to the layer interface are not -//! mutable nor consumable. Functions can be used at any time with no restrictions. The -//! only downside is that the buildpack author (you) is now responsible for: -//! -//! - Ensuring that `Box::section()` was called right before any of these -//! functions are called. -//! - Ensuring that you are not attempting to log while already logging i.e. calling `step()` within a -//! `step_timed()` call. -//! -//! For usage details run `cargo run --bin print_style_guide` -//! -//! ## Use -//! -//! The main use case is logging inside of a layer: -//! -//! ```no_run -//! use libherokubuildpack::output::section_log::log_step; -//! -//! log_step("Clearing the cache") -//! ``` -use crate::output::buildpack_output::StreamLog; -use crate::output::buildpack_output::{state, BuildData, BuildpackOutput}; -use std::io::Stdout; - -/// Output a message as a single step, ideally a short message -/// -/// ``` -/// use libherokubuildpack::output::section_log::log_step; -/// -/// log_step("Clearing cache (ruby version changed)"); -/// ``` -pub fn log_step(s: impl AsRef) { - let _ = logger().step(s.as_ref()); -} - -/// Will print the input string and yield a `Box` that can be used to print -/// to the output. The main use case is running commands -/// -/// ```no_run -/// use fun_run::CommandWithName; -/// use libherokubuildpack::output::section_log::log_step_stream; -/// use libherokubuildpack::output::style; -/// -/// let mut cmd = std::process::Command::new("bundle"); -/// cmd.arg("install"); -/// -/// log_step_stream(format!("Running {}", style::command(cmd.name())), |stream| { -/// cmd.stream_output(stream.io(), stream.io()).unwrap() -/// }); -/// ``` -/// -/// Timing information will be output at the end of the step. -pub fn log_step_stream(s: impl AsRef, f: impl FnOnce(&mut StreamLog) -> T) -> T { - let mut stream = logger().step_timed_stream(s.as_ref()); - let out = f(&mut stream); - let _ = stream.finish_timed_stream(); - out -} - -/// Print an error block to the output -pub fn log_error(s: impl AsRef) { - logger().announce().error(s.as_ref()); -} - -/// Print an warning block to the output -pub fn log_warning(s: impl AsRef) { - let _ = logger().announce().warning(s.as_ref()); -} - -/// Print an important block to the output -pub fn log_important(s: impl AsRef) { - let _ = logger().announce().important(s.as_ref()); -} - -fn logger() -> BuildpackOutput { - BuildpackOutput:: { - io: std::io::stdout(), - // Be careful not to do anything that might access this state - // as it's ephemeral data (i.e. not passed in from the start of the build) - data: BuildData::default(), - _state: state::InSection, - } -}