vector/
cli.rs

1#![allow(missing_docs)]
2
3use std::{num::NonZeroU64, path::PathBuf};
4
5use clap::{ArgAction, CommandFactory, FromArgMatches, Parser};
6
7#[cfg(windows)]
8use crate::service;
9#[cfg(feature = "api-client")]
10use crate::tap;
11#[cfg(feature = "api-client")]
12use crate::top;
13use crate::{
14    config, convert_config, generate, generate_schema, get_version, graph, list, signal, unit_test,
15    validate,
16};
17
18#[derive(Parser, Debug)]
19#[command(rename_all = "kebab-case")]
20pub struct Opts {
21    #[command(flatten)]
22    pub root: RootOpts,
23
24    #[command(subcommand)]
25    pub sub_command: Option<SubCommand>,
26}
27
28impl Opts {
29    pub fn get_matches() -> Result<Self, clap::Error> {
30        let version = get_version();
31        let app = Opts::command().version(version);
32        Opts::from_arg_matches(&app.get_matches())
33    }
34
35    pub const fn log_level(&self) -> &'static str {
36        let (quiet_level, verbose_level) = match self.sub_command {
37            Some(SubCommand::Validate(_))
38            | Some(SubCommand::Graph(_))
39            | Some(SubCommand::Generate(_))
40            | Some(SubCommand::ConvertConfig(_))
41            | Some(SubCommand::List(_))
42            | Some(SubCommand::Test(_)) => {
43                if self.root.verbose == 0 {
44                    (self.root.quiet + 1, self.root.verbose)
45                } else {
46                    (self.root.quiet, self.root.verbose - 1)
47                }
48            }
49            _ => (self.root.quiet, self.root.verbose),
50        };
51        match quiet_level {
52            0 => match verbose_level {
53                0 => "info",
54                1 => "debug",
55                2..=255 => "trace",
56            },
57            1 => "warn",
58            2 => "error",
59            3..=255 => "off",
60        }
61    }
62}
63
64#[derive(Parser, Debug)]
65#[command(rename_all = "kebab-case")]
66pub struct RootOpts {
67    /// Read configuration from one or more files. Wildcard paths are supported.
68    /// File format is detected from the file name.
69    /// If zero files are specified, the deprecated default config path
70    /// `/etc/vector/vector.yaml` is targeted.
71    #[arg(
72        id = "config",
73        short,
74        long,
75        env = "VECTOR_CONFIG",
76        value_delimiter(',')
77    )]
78    pub config_paths: Vec<PathBuf>,
79
80    /// Read configuration from files in one or more directories.
81    /// File format is detected from the file name.
82    ///
83    /// Files not ending in .toml, .json, .yaml, or .yml will be ignored.
84    #[arg(
85        id = "config-dir",
86        short = 'C',
87        long,
88        env = "VECTOR_CONFIG_DIR",
89        value_delimiter(',')
90    )]
91    pub config_dirs: Vec<PathBuf>,
92
93    /// Read configuration from one or more files. Wildcard paths are supported.
94    /// TOML file format is expected.
95    #[arg(
96        id = "config-toml",
97        long,
98        env = "VECTOR_CONFIG_TOML",
99        value_delimiter(',')
100    )]
101    pub config_paths_toml: Vec<PathBuf>,
102
103    /// Read configuration from one or more files. Wildcard paths are supported.
104    /// JSON file format is expected.
105    #[arg(
106        id = "config-json",
107        long,
108        env = "VECTOR_CONFIG_JSON",
109        value_delimiter(',')
110    )]
111    pub config_paths_json: Vec<PathBuf>,
112
113    /// Read configuration from one or more files. Wildcard paths are supported.
114    /// YAML file format is expected.
115    #[arg(
116        id = "config-yaml",
117        long,
118        env = "VECTOR_CONFIG_YAML",
119        value_delimiter(',')
120    )]
121    pub config_paths_yaml: Vec<PathBuf>,
122
123    /// Exit on startup if any sinks fail healthchecks
124    #[arg(short, long, env = "VECTOR_REQUIRE_HEALTHY")]
125    pub require_healthy: Option<bool>,
126
127    /// Number of threads to use for processing (default is number of available cores)
128    #[arg(short, long, env = "VECTOR_THREADS")]
129    pub threads: Option<usize>,
130
131    /// Enable more detailed internal logging. Repeat to increase level. Overridden by `--quiet`.
132    #[arg(short, long, action = ArgAction::Count)]
133    pub verbose: u8,
134
135    /// Reduce detail of internal logging. Repeat to reduce further. Overrides `--verbose`.
136    #[arg(short, long, action = ArgAction::Count)]
137    pub quiet: u8,
138
139    /// Set the logging format
140    #[arg(long, default_value = "text", env = "VECTOR_LOG_FORMAT")]
141    pub log_format: LogFormat,
142
143    /// Control when ANSI terminal formatting is used.
144    ///
145    /// By default `vector` will try and detect if `stdout` is a terminal, if it is
146    /// ANSI will be enabled. Otherwise it will be disabled. By providing this flag with
147    /// the `--color always` option will always enable ANSI terminal formatting. `--color never`
148    /// will disable all ANSI terminal formatting. `--color auto` will attempt
149    /// to detect it automatically.
150    #[arg(long, default_value = "auto", env = "VECTOR_COLOR")]
151    pub color: Color,
152
153    /// Watch for changes in configuration file, and reload accordingly.
154    #[arg(short, long, env = "VECTOR_WATCH_CONFIG")]
155    pub watch_config: bool,
156
157    /// Method for configuration watching.
158    ///
159    /// By default, `vector` uses recommended watcher for host OS
160    /// - `inotify` for Linux-based systems.
161    /// - `kqueue` for unix/macos
162    /// - `ReadDirectoryChangesWatcher` for windows
163    ///
164    /// The `poll` watcher can be used in cases where `inotify` doesn't work, e.g., when attaching the configuration via NFS.
165    #[arg(
166        long,
167        default_value = "recommended",
168        env = "VECTOR_WATCH_CONFIG_METHOD"
169    )]
170    pub watch_config_method: WatchConfigMethod,
171
172    /// Poll for changes in the configuration file at the given interval.
173    ///
174    /// This setting is only applicable if `Poll` is set in `--watch-config-method`.
175    #[arg(
176        long,
177        env = "VECTOR_WATCH_CONFIG_POLL_INTERVAL_SECONDS",
178        default_value = "30"
179    )]
180    pub watch_config_poll_interval_seconds: NonZeroU64,
181
182    /// Set the internal log rate limit in seconds.
183    ///
184    /// This controls the time window for rate limiting Vector's own internal logs.
185    /// Within each time window, the first occurrence of a log is emitted, the second
186    /// shows a suppression warning, and subsequent occurrences are silent until the
187    /// window expires.
188    ///
189    /// Logs are grouped by their location in the code and contextual fields so different log instances can be rate
190    /// limited independently.
191    ///
192    /// Examples:
193    /// - 1: Very verbose, logs can repeat every second
194    /// - 10 (default): Logs can repeat every 10 seconds
195    /// - 60: Less verbose, logs can repeat every minute
196    #[arg(
197        short,
198        long,
199        env = "VECTOR_INTERNAL_LOG_RATE_LIMIT",
200        default_value = "10"
201    )]
202    pub internal_log_rate_limit: u64,
203
204    /// Set the duration in seconds to wait for graceful shutdown after SIGINT or SIGTERM are
205    /// received. After the duration has passed, Vector will force shutdown. To never force
206    /// shutdown, use `--no-graceful-shutdown-limit`.
207    #[arg(
208        long,
209        default_value = "60",
210        env = "VECTOR_GRACEFUL_SHUTDOWN_LIMIT_SECS",
211        group = "graceful-shutdown-limit"
212    )]
213    pub graceful_shutdown_limit_secs: NonZeroU64,
214
215    /// Never time out while waiting for graceful shutdown after SIGINT or SIGTERM received.
216    /// This is useful when you would like for Vector to attempt to send data until terminated
217    /// by a SIGKILL. Overrides/cannot be set with `--graceful-shutdown-limit-secs`.
218    #[arg(
219        long,
220        default_value = "false",
221        env = "VECTOR_NO_GRACEFUL_SHUTDOWN_LIMIT",
222        group = "graceful-shutdown-limit"
223    )]
224    pub no_graceful_shutdown_limit: bool,
225
226    /// Set runtime allocation tracing
227    #[cfg(feature = "allocation-tracing")]
228    #[arg(long, env = "ALLOCATION_TRACING", default_value = "false")]
229    pub allocation_tracing: bool,
230
231    /// Set allocation tracing reporting rate in milliseconds.
232    #[cfg(feature = "allocation-tracing")]
233    #[arg(
234        long,
235        env = "ALLOCATION_TRACING_REPORTING_INTERVAL_MS",
236        default_value = "5000"
237    )]
238    pub allocation_tracing_reporting_interval_ms: u64,
239
240    /// Disable probing and configuration of root certificate locations on the system for OpenSSL.
241    ///
242    /// The probe functionality manipulates the `SSL_CERT_FILE` and `SSL_CERT_DIR` environment variables
243    /// in the Vector process. This behavior can be problematic for users of the `exec` source, which by
244    /// default inherits the environment of the Vector process.
245    #[arg(long, env = "VECTOR_OPENSSL_NO_PROBE", default_value = "false")]
246    pub openssl_no_probe: bool,
247
248    /// Allow the configuration to run without any components. This is useful for loading in an
249    /// empty stub config that will later be replaced with actual components. Note that this is
250    /// likely not useful without also watching for config file changes as described in
251    /// `--watch-config`.
252    #[arg(long, env = "VECTOR_ALLOW_EMPTY_CONFIG", default_value = "false")]
253    pub allow_empty_config: bool,
254}
255
256impl RootOpts {
257    /// Return a list of config paths with the associated formats.
258    pub fn config_paths_with_formats(&self) -> Vec<config::ConfigPath> {
259        config::merge_path_lists(vec![
260            (&self.config_paths, None),
261            (&self.config_paths_toml, Some(config::Format::Toml)),
262            (&self.config_paths_json, Some(config::Format::Json)),
263            (&self.config_paths_yaml, Some(config::Format::Yaml)),
264        ])
265        .map(|(path, hint)| config::ConfigPath::File(path, hint))
266        .chain(
267            self.config_dirs
268                .iter()
269                .map(|dir| config::ConfigPath::Dir(dir.to_path_buf())),
270        )
271        .collect()
272    }
273
274    pub fn init_global(&self) {
275        if !self.openssl_no_probe {
276            unsafe {
277                openssl_probe::init_openssl_env_vars();
278            }
279        }
280
281        crate::metrics::init_global().expect("metrics initialization failed");
282    }
283}
284
285#[derive(Parser, Debug)]
286#[command(rename_all = "kebab-case")]
287pub enum SubCommand {
288    /// Validate the target config, then exit.
289    Validate(validate::Opts),
290
291    /// Convert a config file from one format to another.
292    /// This command can also walk directories recursively and convert all config files that are discovered.
293    /// Note that this is a best effort conversion due to the following reasons:
294    /// * The comments from the original config file are not preserved.
295    /// * Explicitly set default values in the original implementation might be omitted.
296    /// * Depending on how each source/sink config struct configures serde, there might be entries with null values.
297    ConvertConfig(convert_config::Opts),
298
299    /// Generate a Vector configuration containing a list of components.
300    Generate(generate::Opts),
301
302    /// Generate the configuration schema for this version of Vector. (experimental)
303    ///
304    /// A JSON Schema document will be generated that represents the valid schema for a
305    /// Vector configuration. This schema is based on the "full" configuration, such that for usages
306    /// where a configuration is split into multiple files, the schema would apply to those files
307    /// only when concatenated together.
308    ///
309    /// By default all output is writen to stdout. The `output_path` option can be used to redirect to a file.
310    GenerateSchema(generate_schema::Opts),
311
312    /// Output a provided Vector configuration file/dir as a single JSON object, useful for checking in to version control.
313    #[command(hide = true)]
314    Config(config::Opts),
315
316    /// List available components, then exit.
317    List(list::Opts),
318
319    /// Run Vector config unit tests, then exit. This command is experimental and therefore subject to change.
320    /// For guidance on how to write unit tests check out <https://vector.dev/guides/level-up/unit-testing/>.
321    Test(unit_test::Opts),
322
323    /// Output the topology as visual representation using the DOT language which can be rendered by GraphViz
324    Graph(graph::Opts),
325
326    /// Display topology and metrics in the console, for a local or remote Vector instance
327    #[cfg(feature = "api-client")]
328    Top(top::Opts),
329
330    /// Observe output log events from source or transform components. Logs are sampled at a specified interval.
331    #[cfg(feature = "api-client")]
332    Tap(tap::Opts),
333
334    /// Manage the vector service.
335    #[cfg(windows)]
336    Service(service::Opts),
337
338    /// Vector Remap Language CLI
339    Vrl(vrl::cli::Opts),
340}
341
342impl SubCommand {
343    pub async fn execute(
344        &self,
345        mut signals: signal::SignalPair,
346        color: bool,
347    ) -> exitcode::ExitCode {
348        match self {
349            Self::Config(c) => config::cmd(c),
350            Self::ConvertConfig(opts) => convert_config::cmd(opts),
351            Self::Generate(g) => generate::cmd(g),
352            Self::GenerateSchema(opts) => generate_schema::cmd(opts),
353            Self::Graph(g) => graph::cmd(g),
354            Self::List(l) => list::cmd(l),
355            #[cfg(windows)]
356            Self::Service(s) => service::cmd(s),
357            #[cfg(feature = "api-client")]
358            Self::Tap(t) => tap::cmd(t, signals.receiver).await,
359            Self::Test(t) => unit_test::cmd(t, &mut signals.handler).await,
360            #[cfg(feature = "api-client")]
361            Self::Top(t) => top::cmd(t).await,
362            Self::Validate(v) => validate::validate(v, color).await,
363            Self::Vrl(s) => {
364                let mut functions = vrl::stdlib::all();
365                functions.extend(vector_vrl_functions::all());
366                vrl::cli::cmd::cmd(s, functions)
367            }
368        }
369    }
370}
371
372#[derive(clap::ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
373pub enum Color {
374    Auto,
375    Always,
376    Never,
377}
378
379impl Color {
380    pub fn use_color(&self) -> bool {
381        match self {
382            #[cfg(unix)]
383            Color::Auto => {
384                use std::io::IsTerminal;
385                std::io::stdout().is_terminal()
386            }
387            #[cfg(windows)]
388            Color::Auto => false, // ANSI colors are not supported by cmd.exe
389            Color::Always => true,
390            Color::Never => false,
391        }
392    }
393}
394
395#[derive(clap::ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
396pub enum LogFormat {
397    Text,
398    Json,
399}
400
401#[derive(clap::ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
402pub enum WatchConfigMethod {
403    /// Recommended watcher for the current OS, usually `inotify` for Linux-based systems.
404    Recommended,
405    /// Poll-based watcher, typically used for watching files on EFS/NFS-like network storage systems.
406    /// The interval is determined by  [`RootOpts::watch_config_poll_interval_seconds`].
407    Poll,
408}
409
410pub fn handle_config_errors(errors: Vec<String>) -> exitcode::ExitCode {
411    for error in errors {
412        error!(message = "Configuration error.", %error);
413    }
414
415    exitcode::CONFIG
416}