Skip to content
Merged
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
85 changes: 61 additions & 24 deletions src/emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::{
};

/// Verbosity modes.
#[derive(Clone, Copy)]
#[derive(Clone, Copy, PartialEq, PartialOrd)]
#[pyclass]
pub enum Verbosity {
/// Quiet output. Most messages should not be output at all.
Expand Down Expand Up @@ -62,6 +62,12 @@ struct Emitter {

/// A handle on log handling.
_log_handle: Option<Py<PyAny>>,

/// Whether or not to streamline messages in with "brief" level verbosity.
///
/// With this setting, sending an ephemeral progress message causes
/// each subsequent ephemeral message to become prefixed with the initial message.
streaming_brief: bool,
}

#[pymethods]
Expand All @@ -70,22 +76,25 @@ impl Emitter {
///
/// This also enables the logging features
#[new]
#[pyo3(signature = (log_filepath, verbosity, docs_base_url, greeting, streaming_brief = false))]
fn new(
py: Python<'_>,
log_filepath: String,
verbosity: Verbosity,
docs_base_url: &str,
greeting: String,
streaming_brief: bool,
) -> PyResult<Self> {
crate::printer::printer().init_logger(&log_filepath, &greeting)?;

let _log_handle = Self::setup_external_log_capture(py, verbosity)?;
let _log_handle = Self::setup_external_log_capture(py, verbosity, streaming_brief)?;
Ok(Self {
log_filepath,
docs_base_url: docs_base_url.trim_end_matches('/').to_string(),
verbosity,
greeting,
_log_handle,
streaming_brief,
})
}

Expand Down Expand Up @@ -120,16 +129,17 @@ impl Emitter {
fn set_verbosity(&mut self, new: Verbosity) -> PyResult<()> {
self.verbosity = new;

if let Verbosity::Verbose | Verbosity::Debug | Verbosity::Trace = new {
if new >= Verbosity::Verbose {
let messages = [
self.greeting.clone(),
format!("Logging execution to {}", self.log_filepath),
];
for message in messages {
crate::printer::printer().send(Message {
text: message,
model: MessageType::Info,
model: MessageType::Text,
target: Some(Target::Stderr),
permanent: true,
})?;
}
}
Expand All @@ -150,12 +160,12 @@ impl Emitter {
Verbosity::Debug | Verbosity::Trace => (timestamped.as_ref(), Some(Target::Stderr)),
};
let text = maybe_timestamped.to_string();
let model = MessageType::Debug;

let message = Message {
text,
model,
model: MessageType::Text,
target,
permanent: true,
};

crate::printer::printer().send(message)?;
Expand All @@ -175,12 +185,12 @@ impl Emitter {
_ => Some(Target::Stderr),
};
let text = timestamped.to_string();
let model = MessageType::Debug;

let message = Message {
text,
model,
model: MessageType::Text,
target,
permanent: true,
};

crate::printer::printer().send(message)?;
Expand All @@ -200,12 +210,12 @@ impl Emitter {
_ => None,
};
let text = timestamped.to_string();
let model = MessageType::Trace;

let message = Message {
text,
model,
model: MessageType::Text,
target,
permanent: true,
};

crate::printer::printer().send(message)?;
Expand All @@ -224,6 +234,11 @@ impl Emitter {
fn progress(&mut self, text: &str, mut permanent: bool) -> PyResult<()> {
let timestamped = utils::apply_timestamp(text);

// Clear the existing prefix, as we're beginning progress on a new thing now.
if self.streaming_brief {
self.clear_prefix();
}

let (maybe_timestamped, target) = match self.verbosity {
Verbosity::Quiet => {
permanent = false;
Expand All @@ -239,19 +254,23 @@ impl Emitter {
(timestamped.as_ref(), Some(Target::Stderr))
}
};
let model = if permanent {
MessageType::ProgPersistent
} else {
MessageType::ProgEphemeral
};
let text = maybe_timestamped.to_owned();

let final_text = maybe_timestamped.to_owned();

let message = Message {
text,
model,
text: final_text,
model: MessageType::Text,
target,
permanent,
};

crate::printer::printer().send(message)?;

// If we're in streaming brief mode and the last message was ephemeral, set this message as the new prefix
if matches!(self.verbosity, Verbosity::Brief) && !permanent && self.streaming_brief {
self.set_prefix(text.to_string());
}

Ok(())
}

Expand All @@ -260,16 +279,19 @@ impl Emitter {
/// Ideally used as the final message in a sequence to show a result, as it
/// goes to stdout unlike other message types.
fn message(&mut self, text: String) -> PyResult<()> {
// A message-type emission finalizes the current task and shouldn't have a prefix
self.clear_prefix();

let target = match self.verbosity {
Verbosity::Quiet => None,
_ => Some(Target::Stdout),
};
let model = MessageType::Info;

let message = Message {
text,
model,
model: MessageType::Text,
target,
permanent: true,
};

crate::printer::printer().send(message)?;
Expand All @@ -288,12 +310,12 @@ impl Emitter {
_ => (prefixed.as_str(), Some(Target::Stderr)),
};
let text = maybe_timestamped.to_string();
let model = MessageType::Warning;

let message = Message {
text,
model,
model: MessageType::Text,
target,
permanent: true,
};

crate::printer::printer().send(message)?;
Expand All @@ -313,7 +335,11 @@ impl Emitter {

/// Open a stream context manager to redirect output to a different stream.
#[cfg(unix)]
fn open_stream(&self) -> StreamHandle {
#[pyo3(signature = (prefix = None))]
fn open_stream(&self, prefix: Option<String>) -> StreamHandle {
if let Some(pref) = prefix {
self.set_prefix(pref);
}
StreamHandle::new(self.verbosity)
Comment thread
bepri marked this conversation as resolved.
}

Expand All @@ -331,6 +357,16 @@ impl Emitter {
"Stream context manager not yet supported on Windows.",
))
}

/// Set a prefix for each message.
fn set_prefix(&self, prefix: String) {
crate::printer::printer().set_prefix(prefix);
}

/// Clear the current prefix.
fn clear_prefix(&self) {
crate::printer::printer().clear_prefix();
}
}

impl Emitter {
Expand All @@ -345,8 +381,9 @@ impl Emitter {
fn setup_external_log_capture(
py: Python<'_>,
verbosity: Verbosity,
streaming_brief: bool,
) -> PyResult<Option<Py<PyAny>>> {
let log_handler = LogListener::new(py, verbosity)?;
let log_handler = LogListener::new(py, verbosity, streaming_brief)?;

// Instantiate the Python wrapper for log handling
let py_log_handler = py
Expand Down
15 changes: 10 additions & 5 deletions src/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub struct LogListener {
debug_level: Py<PyInt>,

verbosity: Verbosity,

streaming_brief: bool,
}

#[pymethods]
Expand All @@ -37,15 +39,17 @@ impl LogListener {

// Call `record.getMessage()` from Python and parse it into a Rust string
let mut text: String = record.call_method0(intern!(py, "getMessage"))?.extract()?;
if matches!(self.verbosity, Verbosity::Debug | Verbosity::Trace) {
let permanent = self.verbosity >= Verbosity::Verbose;
if permanent {
text = utils::apply_timestamp(&text).into();
}
let target = self.decide_target(&levelno)?;

let message = Message {
text,
target,
model: crate::printer::MessageType::Debug,
model: crate::printer::MessageType::Text,
permanent,
};

crate::printer::printer().send(message)?;
Expand All @@ -54,10 +58,11 @@ impl LogListener {
}

impl LogListener {
pub fn new(py: Python<'_>, verbosity: Verbosity) -> PyResult<Self> {
pub fn new(py: Python<'_>, verbosity: Verbosity, streaming_brief: bool) -> PyResult<Self> {
Ok(Self {
debug_level: py.import("logging")?.getattr("DEBUG")?.extract()?,
verbosity,
streaming_brief,
})
}

Expand All @@ -73,8 +78,8 @@ impl LogListener {

let comp_op = match self.verbosity {
Verbosity::Quiet => return Ok(None),
Verbosity::Brief => return Ok(None),
Verbosity::Verbose => CompareOp::Gt,
Verbosity::Brief if !self.streaming_brief => return Ok(None),
Verbosity::Verbose | Verbosity::Brief => CompareOp::Gt,
Verbosity::Debug => CompareOp::Ge,
Verbosity::Trace => unreachable!("Checked above"),
};
Expand Down
Loading
Loading