1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use comfy_table::Table;
use edge_schema::pretty_duration::parse_timestamp_or_relative_time;
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
use wasmer_api::backend::gql::Log;

use crate::{
    util::{render::CliRender, Identifier},
    ApiOpts, ListFormatOpts,
};

/// Show an app.
#[derive(clap::Parser, Debug)]
pub struct CmdAppLogs {
    #[clap(flatten)]
    api: ApiOpts,
    #[clap(flatten)]
    fmt: ListFormatOpts,

    /// The date of the earliest log entry.
    ///
    /// Defaults to the last 10 minutes.
    ///
    /// Format:
    /// * RFC 3339 (`2006-01-02T03:04:05-07:00`)
    /// * RFC 2822 (`Mon, 02 Jan 2006 03:04:05 MST`)
    /// * Simple date (`2022-11-11`)
    /// * Unix timestamp (`1136196245`)
    /// * Relative time (`10m` / `-1h`, `1d1h30s`)
    // TODO: should default to trailing logs once trailing is implemented.
    #[clap(long, value_parser = parse_timestamp_or_relative_time)]
    from: Option<OffsetDateTime>,

    /// The date of the latest log entry.
    ///
    /// Format:
    /// * RFC 3339 (`2006-01-02T03:04:05-07:00`)
    /// * RFC 2822 (`Mon, 02 Jan 2006 03:04:05 MST`)
    /// * Simple date (`2022-11-11`)
    /// * Unix timestamp (`1136196245`)
    /// * Relative time (`10m` / `1h`, `1d1h30s`)
    #[clap(long, value_parser = parse_timestamp_or_relative_time)]
    until: Option<OffsetDateTime>,

    /// Maximum log lines to fetch.
    /// Defaults to 1000.
    #[clap(long, default_value = "1000")]
    max: usize,

    /// The name of the app.
    ///
    /// Eg:
    /// - name (assumes current user)
    /// - namespace/name
    /// - namespace/name@version
    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: Vec<Log> = wasmer_api::backend::get_app_logs_paginated(
            &client,
            name.clone(),
            owner.to_string(),
            version.clone(),
            from,
            self.until,
            self.max,
        )
        .await?;

        let rendered = self.fmt.format.render(&logs);
        println!("{rendered}");

        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())
    }
}