use comfy_table::Table;
use edge_schema::pretty_duration::parse_timestamp_or_relative_time;
use futures::StreamExt;
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
use wasmer_api::backend::gql::Log;
use crate::{
util::{render::CliRender, Identifier},
ApiOpts, ListFormatOpts,
};
#[derive(clap::Parser, Debug)]
pub struct CmdAppLogs {
#[clap(flatten)]
api: ApiOpts,
#[clap(flatten)]
fmt: ListFormatOpts,
#[clap(long, value_parser = parse_timestamp_or_relative_time)]
from: Option<OffsetDateTime>,
#[clap(long, value_parser = parse_timestamp_or_relative_time)]
until: Option<OffsetDateTime>,
#[clap(long, default_value = "1000")]
max: usize,
#[clap(long, default_value = "false")]
watch: bool,
ident: Identifier,
}
impl CmdAppLogs {
pub async fn run(self) -> Result<(), anyhow::Error> {
let client = self.api.client()?;
let Identifier {
name,
owner,
version,
} = &self.ident;
let owner = match owner {
Some(owner) => owner.to_string(),
None => {
let user = wasmer_api::backend::current_user_with_namespaces(&client, None).await?;
user.username
}
};
let from = self
.from
.unwrap_or_else(|| OffsetDateTime::now_utc() - time::Duration::minutes(10));
tracing::info!(
package.name=%self.ident.name,
package.owner=%owner,
package.version=self.ident.version.as_deref(),
range.start=%from,
range.end=self.until.map(|ts| ts.to_string()),
"Fetching logs",
);
let logs_stream = wasmer_api::backend::get_app_logs_paginated(
&client,
name.clone(),
owner.to_string(),
version.clone(),
from,
self.until,
self.watch,
)
.await;
let mut logs_stream = std::pin::pin!(logs_stream);
let mut rem = self.max;
while let Some(logs) = logs_stream.next().await {
let logs = logs?;
let limit = std::cmp::max(logs.len(), rem);
let logs = &logs[..limit];
if !logs.is_empty() {
let rendered = self.fmt.format.render(logs);
println!("{rendered}");
rem -= limit;
}
if !self.watch && rem == 0 {
break;
}
}
Ok(())
}
}
impl CliRender for Log {
fn render_item_table(&self) -> String {
let mut table = Table::new();
let Log { message, timestamp } = self;
table.add_rows([
vec![
"Timestamp".to_string(),
datetime_from_unix(*timestamp).format(&Rfc3339).unwrap(),
],
vec!["Message".to_string(), message.to_string()],
]);
table.to_string()
}
fn render_list_table(items: &[Self]) -> String {
let mut table = Table::new();
table.set_header(vec!["Timestamp".to_string(), "Message".to_string()]);
for item in items {
table.add_row([
datetime_from_unix(item.timestamp).format(&Rfc3339).unwrap(),
item.message.clone(),
]);
}
table.to_string()
}
}
fn datetime_from_unix(timestamp: f64) -> OffsetDateTime {
OffsetDateTime::from_unix_timestamp_nanos(timestamp as i128)
.expect("Timestamp should always be valid")
}
impl crate::cmd::AsyncCliCommand for CmdAppLogs {
fn run_async(self) -> futures::future::BoxFuture<'static, Result<(), anyhow::Error>> {
Box::pin(self.run())
}
}