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
147
// Copyright (C) 2026 Ordinary Labs, LLC.
//
// SPDX-License-Identifier: AGPL-3.0-only
use clap::Subcommand;
use comfy_table::Table;
use ordinary_monitor::LogFileMetadata;
use std::collections::BTreeMap;
#[derive(Clone, Debug)]
pub enum LogFormat {
/// all logs that match query
All,
/// top logs that match query
Top,
/// count of logs that match query
Count,
}
impl LogFormat {
pub fn as_str(&self) -> &'static str {
match self {
Self::All => "all",
Self::Top => "top",
Self::Count => "count",
}
}
}
impl clap::ValueEnum for LogFormat {
fn value_variants<'a>() -> &'a [Self] {
&[Self::All, Self::Top, Self::Count]
}
fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
match self {
Self::All => Some(clap::builder::PossibleValue::new("all")),
Self::Top => Some(clap::builder::PossibleValue::new("top")),
Self::Count => Some(clap::builder::PossibleValue::new("count")),
}
}
}
#[derive(Subcommand, Debug)]
pub enum Logs {
/// sync files, indexes and tables
Sync {
#[command(subcommand)]
sync: Sync,
},
/// search the tantivy index
Search {
/// format
format: LogFormat,
/// [reference](https://quickwit.io/docs/reference/query-language)
query: String,
#[arg(long)]
/// limit (when using 'top' format)
limit: Option<usize>,
#[arg(short, long)]
/// whether to sync from remote
sync: Option<bool>,
},
}
#[derive(Subcommand, Debug)]
pub enum Sync {
/// get information about remote and local log files.
///
/// - "✅" remote and local are synced
/// - "❌" not found on remote *or* local
/// - "⚠️" present on both but local is out of sync
Info,
/// sync a single file.
File {
/// file name
name: String,
},
/// repair local state for all out of sync (⚠️) and remote
/// files not yet downloaded (❌).
All {
#[arg(short, long)]
/// download all files from the server,
/// overwriting everything you have locally.
///
/// Note: will preserve the state of any files the server
/// no longer has a record of.
force: Option<bool>,
},
}
pub fn print_logs_metadata_table(
remote_metadata_vec: Vec<LogFileMetadata>,
local_metadata_vec: Vec<LogFileMetadata>,
) {
let mut table = Table::new();
table.set_header(vec!["file", "size", "remote", "local"]);
let mut rows_map = BTreeMap::new();
for metadata in remote_metadata_vec {
rows_map.insert(
metadata.name.clone(),
vec![
metadata.name.clone(),
format!("{}", bytesize::ByteSize(metadata.size).display().si_short()),
"✅".to_string(),
"❌".to_string(),
],
);
}
for metadata in local_metadata_vec {
if let Some(row) = rows_map.get_mut(&metadata.name) {
let local_size = format!("{}", bytesize::ByteSize(metadata.size).display().si_short());
if row[1] == local_size {
row[3] = "✅".to_string();
} else {
row[3] = "⚠️".to_string();
}
row[1] = format!("{local_size}/{}", row[1]);
} else {
rows_map.insert(
metadata.name.clone(),
vec![
metadata.name.clone(),
format!("{}", bytesize::ByteSize(metadata.size).display().si_short()),
"❌".to_string(),
"✅".to_string(),
],
);
}
}
for (_, row) in rows_map {
table.add_row(row);
}
println!("\n{table}\n");
}