log: Add tracing events through core for future debugability.
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
//! caching: unchanged stages keep their results.
|
//! caching: unchanged stages keep their results.
|
||||||
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
use super::filter::{Filter, FuzzyFilter};
|
use super::filter::{Filter, FuzzyFilter};
|
||||||
use super::strategy::{self, FilterKind};
|
use super::strategy::{self, FilterKind};
|
||||||
@@ -135,11 +136,15 @@ impl FilterPipeline {
|
|||||||
/// Evaluate all dirty stages in order. Each stage filters
|
/// Evaluate all dirty stages in order. Each stage filters
|
||||||
/// against the previous stage's cached_indices.
|
/// against the previous stage's cached_indices.
|
||||||
fn evaluate(&mut self) {
|
fn evaluate(&mut self) {
|
||||||
|
let _span = tracing::trace_span!("pipeline_evaluate").entered();
|
||||||
for stage_idx in 0..self.stages.len() {
|
for stage_idx in 0..self.stages.len() {
|
||||||
if !self.stages[stage_idx].dirty {
|
if !self.stages[stage_idx].dirty {
|
||||||
|
trace!(stage_idx, dirty = false, "skipping clean stage");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trace!(stage_idx, dirty = true, kind = ?self.stages[stage_idx].kind, "evaluating stage");
|
||||||
|
|
||||||
let input_indices: Vec<usize> = if stage_idx == 0 {
|
let input_indices: Vec<usize> = if stage_idx == 0 {
|
||||||
self.items.iter().map(|(idx, _)| *idx).collect()
|
self.items.iter().map(|(idx, _)| *idx).collect()
|
||||||
} else {
|
} else {
|
||||||
@@ -174,6 +179,7 @@ impl FilterPipeline {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
trace!(stage_idx, matched = result.len(), "stage complete");
|
||||||
self.stages[stage_idx].cached_indices = result;
|
self.stages[stage_idx].cached_indices = result;
|
||||||
self.stages[stage_idx].dirty = false;
|
self.stages[stage_idx].dirty = false;
|
||||||
}
|
}
|
||||||
@@ -275,6 +281,7 @@ impl FilterPipeline {
|
|||||||
/// items. Used after replace_all or remove_by_indices
|
/// items. Used after replace_all or remove_by_indices
|
||||||
/// to rebuild the pipeline from scratch.
|
/// to rebuild the pipeline from scratch.
|
||||||
pub fn rebuild(&mut self, items: &[(usize, &str)]) {
|
pub fn rebuild(&mut self, items: &[(usize, &str)]) {
|
||||||
|
debug!(item_count = items.len(), "pipeline rebuilt");
|
||||||
self.items.clear();
|
self.items.clear();
|
||||||
self.item_values.clear();
|
self.item_values.clear();
|
||||||
self.stages.clear();
|
self.stages.clear();
|
||||||
@@ -288,6 +295,7 @@ impl FilterPipeline {
|
|||||||
/// Clear all items and stages, then re-push with values.
|
/// Clear all items and stages, then re-push with values.
|
||||||
/// Used when field filters need access to item JSON.
|
/// Used when field filters need access to item JSON.
|
||||||
pub fn rebuild_with_values(&mut self, items: &[(usize, &str, &Value)]) {
|
pub fn rebuild_with_values(&mut self, items: &[(usize, &str, &Value)]) {
|
||||||
|
debug!(item_count = items.len(), "pipeline rebuilt");
|
||||||
self.items.clear();
|
self.items.clear();
|
||||||
self.item_values.clear();
|
self.item_values.clear();
|
||||||
self.stages.clear();
|
self.stages.clear();
|
||||||
@@ -325,6 +333,7 @@ impl Filter for FilterPipeline {
|
|||||||
fn set_query(&mut self, query: &str) {
|
fn set_query(&mut self, query: &str) {
|
||||||
self.last_raw_query = query.to_string();
|
self.last_raw_query = query.to_string();
|
||||||
let segments = split_pipeline(query);
|
let segments = split_pipeline(query);
|
||||||
|
debug!(query = %query, stage_count = segments.len(), "pipeline query set");
|
||||||
|
|
||||||
// Reconcile stages with new segments
|
// Reconcile stages with new segments
|
||||||
let mut new_len = segments.len();
|
let mut new_len = segments.len();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
use tracing::Instrument;
|
||||||
|
|
||||||
use crate::event::Action;
|
use crate::event::Action;
|
||||||
use crate::hook::{HookEvent, HookEventKind, HookHandler, HookResponse};
|
use crate::hook::{HookEvent, HookEventKind, HookHandler, HookResponse};
|
||||||
@@ -76,6 +77,8 @@ impl DebouncedDispatcher {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or(DebounceMode::None);
|
.unwrap_or(DebounceMode::None);
|
||||||
|
|
||||||
|
tracing::debug!(event_kind = ?kind, mode = ?mode, "dispatching hook event");
|
||||||
|
|
||||||
match mode {
|
match mode {
|
||||||
DebounceMode::None => {
|
DebounceMode::None => {
|
||||||
self.fire_now(event);
|
self.fire_now(event);
|
||||||
@@ -95,11 +98,15 @@ impl DebouncedDispatcher {
|
|||||||
|
|
||||||
fn fire_now(&self, event: HookEvent) {
|
fn fire_now(&self, event: HookEvent) {
|
||||||
let handler = Arc::clone(&self.handler);
|
let handler = Arc::clone(&self.handler);
|
||||||
tokio::spawn(async move {
|
let span = tracing::debug_span!("hook_fire", event_kind = ?event.kind());
|
||||||
|
tokio::spawn(
|
||||||
|
async move {
|
||||||
if let Err(e) = handler.handle(event) {
|
if let Err(e) = handler.handle(event) {
|
||||||
tracing::warn!(error = %e, "hook handler error");
|
tracing::warn!(error = %e, "hook handler error");
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
.instrument(span),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fire_debounced(&mut self, event: HookEvent, delay: Duration, cancel: bool) {
|
fn fire_debounced(&mut self, event: HookEvent, delay: Duration, cancel: bool) {
|
||||||
@@ -108,19 +115,27 @@ impl DebouncedDispatcher {
|
|||||||
self.cancel_in_flight(kind);
|
self.cancel_in_flight(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let delay_ms = delay.as_millis() as u64;
|
||||||
|
tracing::debug!(delay_ms, cancel_stale = cancel, "debounced hook scheduled");
|
||||||
|
|
||||||
let handler = Arc::clone(&self.handler);
|
let handler = Arc::clone(&self.handler);
|
||||||
let handle = tokio::spawn(async move {
|
let span = tracing::debug_span!("hook_fire", event_kind = ?kind);
|
||||||
|
let handle = tokio::spawn(
|
||||||
|
async move {
|
||||||
tokio::time::sleep(delay).await;
|
tokio::time::sleep(delay).await;
|
||||||
if let Err(e) = handler.handle(event) {
|
if let Err(e) = handler.handle(event) {
|
||||||
tracing::warn!(error = %e, "hook handler error");
|
tracing::warn!(error = %e, "hook handler error");
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
.instrument(span),
|
||||||
|
);
|
||||||
|
|
||||||
self.in_flight.insert(kind, handle);
|
self.in_flight.insert(kind, handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cancel_in_flight(&mut self, kind: HookEventKind) {
|
fn cancel_in_flight(&mut self, kind: HookEventKind) {
|
||||||
if let Some(handle) = self.in_flight.remove(&kind) {
|
if let Some(handle) = self.in_flight.remove(&kind) {
|
||||||
|
tracing::debug!(event_kind = ?kind, "cancelled in-flight hook");
|
||||||
handle.abort();
|
handle.abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,10 @@ pub trait HookHandler: Send + Sync {
|
|||||||
/// tracing.
|
/// tracing.
|
||||||
pub fn parse_hook_response(line: &str) -> Option<HookResponse> {
|
pub fn parse_hook_response(line: &str) -> Option<HookResponse> {
|
||||||
match serde_json::from_str::<HookResponse>(line) {
|
match serde_json::from_str::<HookResponse>(line) {
|
||||||
Ok(resp) => Some(resp),
|
Ok(resp) => {
|
||||||
|
tracing::debug!(response = ?resp, "parsed hook response");
|
||||||
|
Some(resp)
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!(line, error = %e, "failed to parse hook response");
|
tracing::warn!(line, error = %e, "failed to parse hook response");
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ pub fn read_items_sync(
|
|||||||
}
|
}
|
||||||
items.push(parse_line(&line, label_key));
|
items.push(parse_line(&line, label_key));
|
||||||
}
|
}
|
||||||
|
tracing::debug!(count = items.len(), "items read");
|
||||||
Ok(items)
|
Ok(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ pub async fn read_items(
|
|||||||
}
|
}
|
||||||
items.push(parse_line(&line, label_key));
|
items.push(parse_line(&line, label_key));
|
||||||
}
|
}
|
||||||
|
tracing::debug!(count = items.len(), "items read");
|
||||||
Ok(items)
|
Ok(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ pub struct JsonMenu {
|
|||||||
impl JsonMenu {
|
impl JsonMenu {
|
||||||
/// Create a new JSON menu with the given items and label key.
|
/// Create a new JSON menu with the given items and label key.
|
||||||
pub fn new(items: Vec<Item>, label_key: String) -> Self {
|
pub fn new(items: Vec<Item>, label_key: String) -> Self {
|
||||||
|
let item_count = items.len();
|
||||||
|
tracing::debug!(item_count, label_key = %label_key, "json menu created");
|
||||||
let mut filter = FilterPipeline::new();
|
let mut filter = FilterPipeline::new();
|
||||||
for (i, item) in items.iter().enumerate() {
|
for (i, item) in items.iter().enumerate() {
|
||||||
filter.push_with_value(i, item.label(), &item.value);
|
filter.push_with_value(i, item.label(), &item.value);
|
||||||
@@ -139,6 +141,7 @@ impl Menu for JsonMenu {
|
|||||||
|
|
||||||
impl MutableMenu for JsonMenu {
|
impl MutableMenu for JsonMenu {
|
||||||
fn add_raw(&mut self, values: Vec<serde_json::Value>) {
|
fn add_raw(&mut self, values: Vec<serde_json::Value>) {
|
||||||
|
let count = values.len();
|
||||||
for value in values {
|
for value in values {
|
||||||
let idx = self.items.len();
|
let idx = self.items.len();
|
||||||
let item = Item::new(value, &self.label_key);
|
let item = Item::new(value, &self.label_key);
|
||||||
@@ -146,9 +149,11 @@ impl MutableMenu for JsonMenu {
|
|||||||
self.filter.push_with_value(idx, &text, &item.value);
|
self.filter.push_with_value(idx, &text, &item.value);
|
||||||
self.items.push(item);
|
self.items.push(item);
|
||||||
}
|
}
|
||||||
|
tracing::debug!(count, new_total = self.items.len(), "adding items to menu");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_all(&mut self, values: Vec<serde_json::Value>) {
|
fn replace_all(&mut self, values: Vec<serde_json::Value>) {
|
||||||
|
tracing::debug!(count = values.len(), "replacing all menu items");
|
||||||
self.items = values
|
self.items = values
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|v| Item::new(v, &self.label_key))
|
.map(|v| Item::new(v, &self.label_key))
|
||||||
@@ -157,6 +162,7 @@ impl MutableMenu for JsonMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn remove_by_indices(&mut self, indices: Vec<usize>) {
|
fn remove_by_indices(&mut self, indices: Vec<usize>) {
|
||||||
|
let count = indices.len();
|
||||||
// Sort descending to remove from the end first,
|
// Sort descending to remove from the end first,
|
||||||
// preserving earlier indices.
|
// preserving earlier indices.
|
||||||
let mut sorted = indices;
|
let mut sorted = indices;
|
||||||
@@ -167,6 +173,7 @@ impl MutableMenu for JsonMenu {
|
|||||||
self.items.remove(idx);
|
self.items.remove(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tracing::debug!(count, remaining = self.items.len(), "items removed from menu");
|
||||||
self.rebuild_pipeline();
|
self.rebuild_pipeline();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use tokio::sync::{broadcast, mpsc};
|
use tokio::sync::{broadcast, mpsc};
|
||||||
|
use tracing::{debug, info, trace};
|
||||||
|
|
||||||
use crate::debounce::{hook_response_to_action, DebouncedDispatcher};
|
use crate::debounce::{hook_response_to_action, DebouncedDispatcher};
|
||||||
use crate::error::PiklError;
|
use crate::error::PiklError;
|
||||||
@@ -101,8 +102,19 @@ impl<M: MutableMenu> MenuRunner<M> {
|
|||||||
/// Re-run the filter against all items with the current
|
/// Re-run the filter against all items with the current
|
||||||
/// filter text. Updates the viewport with the new count.
|
/// filter text. Updates the viewport with the new count.
|
||||||
fn run_filter(&mut self) {
|
fn run_filter(&mut self) {
|
||||||
|
let start = std::time::Instant::now();
|
||||||
self.menu.apply_filter(&self.filter_text);
|
self.menu.apply_filter(&self.filter_text);
|
||||||
self.viewport.set_filtered_count(self.menu.filtered_count());
|
let matched = self.menu.filtered_count();
|
||||||
|
let total = self.menu.total();
|
||||||
|
let duration_us = start.elapsed().as_micros() as u64;
|
||||||
|
debug!(
|
||||||
|
filter = %self.filter_text,
|
||||||
|
matched,
|
||||||
|
total,
|
||||||
|
duration_us,
|
||||||
|
"filter applied"
|
||||||
|
);
|
||||||
|
self.viewport.set_filtered_count(matched);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a [`ViewState`] snapshot from the current filter
|
/// Build a [`ViewState`] snapshot from the current filter
|
||||||
@@ -144,12 +156,18 @@ impl<M: MutableMenu> MenuRunner<M> {
|
|||||||
/// Send the current view state to all subscribers.
|
/// Send the current view state to all subscribers.
|
||||||
fn broadcast_state(&mut self) {
|
fn broadcast_state(&mut self) {
|
||||||
let vs = self.build_view_state();
|
let vs = self.build_view_state();
|
||||||
|
trace!(
|
||||||
|
generation = vs.generation,
|
||||||
|
filtered = vs.total_filtered,
|
||||||
|
"state broadcast"
|
||||||
|
);
|
||||||
let _ = self.event_tx.send(MenuEvent::StateChanged(vs));
|
let _ = self.event_tx.send(MenuEvent::StateChanged(vs));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emit a hook event through the dispatcher, if one is set.
|
/// Emit a hook event through the dispatcher, if one is set.
|
||||||
fn emit_hook(&mut self, event: HookEvent) {
|
fn emit_hook(&mut self, event: HookEvent) {
|
||||||
if let Some(dispatcher) = &mut self.dispatcher {
|
if let Some(dispatcher) = &mut self.dispatcher {
|
||||||
|
debug!(hook_event = ?event.kind(), "hook dispatched");
|
||||||
dispatcher.dispatch(event);
|
dispatcher.dispatch(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,6 +186,7 @@ impl<M: MutableMenu> MenuRunner<M> {
|
|||||||
if let Some(value) = self.menu.serialize_filtered(current).cloned()
|
if let Some(value) = self.menu.serialize_filtered(current).cloned()
|
||||||
&& let Some(orig_idx) = current_orig
|
&& let Some(orig_idx) = current_orig
|
||||||
{
|
{
|
||||||
|
trace!(cursor = current, original_index = orig_idx, "hover changed");
|
||||||
self.emit_hook(HookEvent::Hover {
|
self.emit_hook(HookEvent::Hover {
|
||||||
item: value,
|
item: value,
|
||||||
index: orig_idx,
|
index: orig_idx,
|
||||||
@@ -181,6 +200,7 @@ impl<M: MutableMenu> MenuRunner<M> {
|
|||||||
pub fn apply_action(&mut self, action: Action) -> ActionOutcome {
|
pub fn apply_action(&mut self, action: Action) -> ActionOutcome {
|
||||||
match action {
|
match action {
|
||||||
Action::UpdateFilter(text) => {
|
Action::UpdateFilter(text) => {
|
||||||
|
debug!(filter = %text, "filter updated");
|
||||||
self.filter_text = Arc::from(text);
|
self.filter_text = Arc::from(text);
|
||||||
self.run_filter();
|
self.run_filter();
|
||||||
ActionOutcome::Broadcast
|
ActionOutcome::Broadcast
|
||||||
@@ -237,6 +257,7 @@ impl<M: MutableMenu> MenuRunner<M> {
|
|||||||
}
|
}
|
||||||
Action::Cancel => ActionOutcome::Cancelled,
|
Action::Cancel => ActionOutcome::Cancelled,
|
||||||
Action::Resize { height } => {
|
Action::Resize { height } => {
|
||||||
|
trace!(height, "viewport resized");
|
||||||
self.viewport.set_height(height as usize);
|
self.viewport.set_height(height as usize);
|
||||||
ActionOutcome::Broadcast
|
ActionOutcome::Broadcast
|
||||||
}
|
}
|
||||||
@@ -249,15 +270,18 @@ impl<M: MutableMenu> MenuRunner<M> {
|
|||||||
ActionOutcome::Broadcast
|
ActionOutcome::Broadcast
|
||||||
}
|
}
|
||||||
Action::SetMode(m) => {
|
Action::SetMode(m) => {
|
||||||
|
debug!(mode = ?m, "mode changed");
|
||||||
self.mode = m;
|
self.mode = m;
|
||||||
ActionOutcome::Broadcast
|
ActionOutcome::Broadcast
|
||||||
}
|
}
|
||||||
Action::AddItems(values) => {
|
Action::AddItems(values) => {
|
||||||
|
debug!(count = values.len(), "items added");
|
||||||
self.menu.add_raw(values);
|
self.menu.add_raw(values);
|
||||||
self.run_filter();
|
self.run_filter();
|
||||||
ActionOutcome::Broadcast
|
ActionOutcome::Broadcast
|
||||||
}
|
}
|
||||||
Action::ReplaceItems(values) => {
|
Action::ReplaceItems(values) => {
|
||||||
|
debug!(count = values.len(), "items replaced");
|
||||||
// Smart cursor: try to keep selection on the same original item.
|
// Smart cursor: try to keep selection on the same original item.
|
||||||
let cursor = self.viewport.cursor();
|
let cursor = self.viewport.cursor();
|
||||||
let old_value = self.menu.serialize_filtered(cursor).cloned();
|
let old_value = self.menu.serialize_filtered(cursor).cloned();
|
||||||
@@ -282,6 +306,7 @@ impl<M: MutableMenu> MenuRunner<M> {
|
|||||||
ActionOutcome::Broadcast
|
ActionOutcome::Broadcast
|
||||||
}
|
}
|
||||||
Action::RemoveItems(indices) => {
|
Action::RemoveItems(indices) => {
|
||||||
|
debug!(count = indices.len(), "items removed");
|
||||||
self.menu.remove_by_indices(indices);
|
self.menu.remove_by_indices(indices);
|
||||||
self.run_filter();
|
self.run_filter();
|
||||||
self.viewport.clamp();
|
self.viewport.clamp();
|
||||||
@@ -319,6 +344,10 @@ impl<M: MutableMenu> MenuRunner<M> {
|
|||||||
self.run_filter();
|
self.run_filter();
|
||||||
self.broadcast_state();
|
self.broadcast_state();
|
||||||
|
|
||||||
|
let total_items = self.menu.total();
|
||||||
|
let filtered = self.menu.filtered_count();
|
||||||
|
info!(total_items, filtered, "menu opened");
|
||||||
|
|
||||||
// Emit Open event
|
// Emit Open event
|
||||||
self.emit_hook(HookEvent::Open);
|
self.emit_hook(HookEvent::Open);
|
||||||
|
|
||||||
@@ -339,6 +368,7 @@ impl<M: MutableMenu> MenuRunner<M> {
|
|||||||
self.check_cursor_hover();
|
self.check_cursor_hover();
|
||||||
}
|
}
|
||||||
ActionOutcome::Selected { value, index } => {
|
ActionOutcome::Selected { value, index } => {
|
||||||
|
info!(index, "item selected");
|
||||||
// Emit Select event
|
// Emit Select event
|
||||||
self.emit_hook(HookEvent::Select {
|
self.emit_hook(HookEvent::Select {
|
||||||
item: value.clone(),
|
item: value.clone(),
|
||||||
@@ -354,6 +384,7 @@ impl<M: MutableMenu> MenuRunner<M> {
|
|||||||
let values: Vec<Value> =
|
let values: Vec<Value> =
|
||||||
items.iter().map(|(v, _)| v.clone()).collect();
|
items.iter().map(|(v, _)| v.clone()).collect();
|
||||||
let count = values.len();
|
let count = values.len();
|
||||||
|
info!(count, "quicklist returned");
|
||||||
self.emit_hook(HookEvent::Quicklist {
|
self.emit_hook(HookEvent::Quicklist {
|
||||||
items: values.clone(),
|
items: values.clone(),
|
||||||
count,
|
count,
|
||||||
@@ -366,6 +397,7 @@ impl<M: MutableMenu> MenuRunner<M> {
|
|||||||
return Ok(MenuResult::Quicklist { items });
|
return Ok(MenuResult::Quicklist { items });
|
||||||
}
|
}
|
||||||
ActionOutcome::Cancelled => {
|
ActionOutcome::Cancelled => {
|
||||||
|
info!("menu cancelled");
|
||||||
self.emit_hook(HookEvent::Cancel);
|
self.emit_hook(HookEvent::Cancel);
|
||||||
self.emit_hook(HookEvent::Close);
|
self.emit_hook(HookEvent::Close);
|
||||||
|
|
||||||
@@ -373,6 +405,7 @@ impl<M: MutableMenu> MenuRunner<M> {
|
|||||||
return Ok(MenuResult::Cancelled);
|
return Ok(MenuResult::Cancelled);
|
||||||
}
|
}
|
||||||
ActionOutcome::Closed => {
|
ActionOutcome::Closed => {
|
||||||
|
info!("menu closed by hook");
|
||||||
self.emit_hook(HookEvent::Close);
|
self.emit_hook(HookEvent::Close);
|
||||||
|
|
||||||
let _ = self.event_tx.send(MenuEvent::Cancelled);
|
let _ = self.event_tx.send(MenuEvent::Cancelled);
|
||||||
|
|||||||
@@ -123,7 +123,9 @@ struct Cli {
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Initialize tracing from RUST_LOG env var
|
// Initialize tracing from RUST_LOG env var
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt()
|
||||||
|
.with_writer(std::io::stderr)
|
||||||
|
.init();
|
||||||
|
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user