logcheck_fluent_bit_filter/cli/
args.rs1use clap::{Parser, Subcommand, ValueEnum};
2use clap_complete::Shell;
3use std::path::PathBuf;
4
5#[cfg(target_os = "linux")]
7#[derive(Subcommand, Clone, Debug)]
8pub enum JournaldMode {
9 Analyze {
11 #[arg(long, default_value = "2", help = "Minimum matches to propose pattern")]
13 min_group_size: usize,
14 },
15}
16
17#[derive(Parser)]
19#[command(name = "logcheck-filter")]
20#[command(about = "Filter logs using logcheck rules")]
21#[command(version)]
22pub struct Cli {
23 #[arg(
25 long,
26 default_value = "/etc/logcheck",
27 help = "Path to logcheck rules directory"
28 )]
29 pub rules: PathBuf,
30
31 #[arg(long, value_enum, default_value = "text", help = "Output format")]
33 pub format: OutputFormat,
34
35 #[arg(long, value_enum, default_value = "all", help = "What entries to show")]
37 pub show: ShowMode,
38
39 #[arg(long, help = "Show processing statistics")]
41 pub stats: bool,
42
43 #[arg(long, help = "Enable colored output")]
45 pub color: bool,
46
47 #[arg(long, help = "Write filtered logs to file")]
49 pub output_file: Option<PathBuf>,
50
51 #[arg(
53 long,
54 hide = true,
55 help = "Generate CLI documentation in Markdown format"
56 )]
57 pub generate_docs: bool,
58
59 #[arg(
61 long,
62 value_enum,
63 hide = true,
64 help = "Generate shell completion scripts"
65 )]
66 pub generate_completion: Option<Shell>,
67
68 #[command(subcommand)]
70 pub input: InputSource,
71}
72
73#[derive(Subcommand)]
74pub enum InputSource {
75 File {
77 path: PathBuf,
79 },
80 Stdin,
82 #[cfg(target_os = "linux")]
84 Journald {
85 #[arg(long, help = "Filter by systemd unit")]
87 unit: Option<String>,
88 #[arg(long, help = "Follow new journal entries")]
90 follow: bool,
91 #[arg(long, help = "Show last N entries")]
93 lines: Option<usize>,
94 #[command(subcommand)]
96 mode: Option<JournaldMode>,
97 },
98}
99
100#[derive(ValueEnum, Clone, Debug)]
101pub enum OutputFormat {
102 Text,
104 Json,
106}
107
108#[derive(ValueEnum, Clone, Debug)]
109pub enum ShowMode {
110 All,
112 Violations,
114 Unmatched,
116}
117
118impl Cli {
119 pub fn parse_args() -> Self {
121 Self::parse()
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn test_cli_parsing() {
131 let args = vec![
133 "logcheck-filter",
134 "--rules",
135 "/etc/logcheck",
136 "file",
137 "/var/log/syslog",
138 ];
139 let cli = Cli::try_parse_from(args).unwrap();
140
141 assert_eq!(cli.rules, PathBuf::from("/etc/logcheck"));
142 assert!(matches!(cli.format, OutputFormat::Text));
143 assert!(matches!(cli.show, ShowMode::All));
144 assert!(!cli.stats);
145 assert!(!cli.color);
146 assert!(matches!(cli.input, InputSource::File { .. }));
147 }
148
149 #[test]
150 fn test_cli_default_rules() {
151 let args = vec!["logcheck-filter", "stdin"];
153 let cli = Cli::try_parse_from(args).unwrap();
154
155 assert_eq!(cli.rules, PathBuf::from("/etc/logcheck"));
156 }
157
158 #[test]
159 fn test_cli_with_options() {
160 let args = vec![
161 "logcheck-filter",
162 "--rules",
163 "/etc/logcheck",
164 "--format",
165 "json",
166 "--show",
167 "violations",
168 "--stats",
169 "--color",
170 "stdin",
171 ];
172 let cli = Cli::try_parse_from(args).unwrap();
173
174 assert!(matches!(cli.format, OutputFormat::Json));
175 assert!(matches!(cli.show, ShowMode::Violations));
176 assert!(cli.stats);
177 assert!(cli.color);
178 assert!(matches!(cli.input, InputSource::Stdin));
179 }
180
181 #[test]
182 #[cfg(target_os = "linux")]
183 fn test_journald_options() {
184 let args = vec![
185 "logcheck-filter",
186 "--rules",
187 "/etc/logcheck",
188 "journald",
189 "--unit",
190 "sshd",
191 "--follow",
192 "--lines",
193 "100",
194 ];
195 let cli = Cli::try_parse_from(args).unwrap();
196
197 if let InputSource::Journald {
198 unit,
199 follow,
200 lines,
201 mode,
202 } = cli.input
203 {
204 assert_eq!(unit, Some("sshd".to_string()));
205 assert!(follow);
206 assert_eq!(lines, Some(100));
207 assert!(mode.is_none());
208 } else {
209 panic!("Expected Journald input source");
210 }
211 }
212
213 #[test]
214 #[cfg(target_os = "linux")]
215 fn test_journald_analyze_mode() {
216 let args = vec![
217 "logcheck-filter",
218 "journald",
219 "analyze",
220 "--min-group-size",
221 "5",
222 ];
223 let cli = Cli::try_parse_from(args).unwrap();
224
225 if let InputSource::Journald {
226 unit,
227 follow,
228 lines,
229 mode,
230 } = cli.input
231 {
232 assert_eq!(unit, None);
233 assert!(!follow);
234 assert_eq!(lines, None);
235 assert!(mode.is_some());
236
237 if let Some(JournaldMode::Analyze { min_group_size }) = mode {
238 assert_eq!(min_group_size, 5);
239 } else {
240 panic!("Expected Analyze mode");
241 }
242 } else {
243 panic!("Expected Journald input source");
244 }
245 }
246}