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