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