1#![allow(missing_docs)]
2#[cfg(unix)]
3use std::os::unix::process::ExitStatusExt;
4#[cfg(windows)]
5use std::os::windows::process::ExitStatusExt;
6use std::{
7 num::{NonZeroU64, NonZeroUsize},
8 path::PathBuf,
9 process::ExitStatus,
10 sync::atomic::{AtomicUsize, Ordering},
11 time::Duration,
12};
13
14use exitcode::ExitCode;
15use futures::StreamExt;
16use tokio::{
17 runtime::{self, Handle, Runtime},
18 sync::{MutexGuard, broadcast::error::RecvError},
19};
20use tokio_stream::wrappers::UnboundedReceiverStream;
21
22#[cfg(feature = "api")]
23use crate::api;
24#[cfg(feature = "api")]
25use crate::internal_events::ApiStarted;
26use crate::{
27 cli::{LogFormat, Opts, RootOpts, WatchConfigMethod, handle_config_errors},
28 config::{self, ComponentConfig, ComponentType, Config, ConfigPath},
29 extra_context::ExtraContext,
30 heartbeat,
31 internal_events::{
32 VectorConfigLoadError, VectorQuit, VectorStarted, VectorStopped, VectorStopping,
33 },
34 signal::{SignalHandler, SignalPair, SignalRx, SignalTo},
35 topology::{
36 ReloadOutcome, RunningTopology, SharedTopologyController, ShutdownErrorReceiver,
37 TopologyController,
38 },
39 trace,
40};
41
42static WORKER_THREADS: AtomicUsize = AtomicUsize::new(0);
43
44pub fn worker_threads() -> Option<NonZeroUsize> {
45 NonZeroUsize::new(WORKER_THREADS.load(Ordering::Relaxed))
46}
47
48pub struct ApplicationConfig {
49 pub config_paths: Vec<config::ConfigPath>,
50 pub topology: RunningTopology,
51 pub graceful_crash_receiver: ShutdownErrorReceiver,
52 pub internal_topologies: Vec<RunningTopology>,
53 #[cfg(feature = "api")]
54 pub api: config::api::Options,
55 pub extra_context: ExtraContext,
56}
57
58pub struct Application {
59 pub root_opts: RootOpts,
60 pub config: ApplicationConfig,
61 pub signals: SignalPair,
62}
63
64impl ApplicationConfig {
65 pub async fn from_opts(
66 opts: &RootOpts,
67 signal_handler: &mut SignalHandler,
68 extra_context: ExtraContext,
69 ) -> Result<Self, ExitCode> {
70 let config_paths = opts.config_paths_with_formats();
71
72 let graceful_shutdown_duration = (!opts.no_graceful_shutdown_limit)
73 .then(|| Duration::from_secs(u64::from(opts.graceful_shutdown_limit_secs)));
74
75 let watcher_conf = if opts.watch_config {
76 Some(watcher_config(
77 opts.watch_config_method,
78 opts.watch_config_poll_interval_seconds,
79 ))
80 } else {
81 None
82 };
83
84 let config = load_configs(
85 &config_paths,
86 watcher_conf,
87 opts.require_healthy,
88 opts.allow_empty_config,
89 graceful_shutdown_duration,
90 signal_handler,
91 )
92 .await?;
93
94 Self::from_config(config_paths, config, extra_context).await
95 }
96
97 pub async fn from_config(
98 config_paths: Vec<ConfigPath>,
99 config: Config,
100 extra_context: ExtraContext,
101 ) -> Result<Self, ExitCode> {
102 #[cfg(feature = "api")]
103 let api = config.api;
104
105 let (topology, graceful_crash_receiver) =
106 RunningTopology::start_init_validated(config, extra_context.clone())
107 .await
108 .ok_or(exitcode::CONFIG)?;
109
110 Ok(Self {
111 config_paths,
112 topology,
113 graceful_crash_receiver,
114 internal_topologies: Vec::new(),
115 #[cfg(feature = "api")]
116 api,
117 extra_context,
118 })
119 }
120
121 pub async fn add_internal_config(
122 &mut self,
123 config: Config,
124 extra_context: ExtraContext,
125 ) -> Result<(), ExitCode> {
126 let Some((topology, _)) =
127 RunningTopology::start_init_validated(config, extra_context).await
128 else {
129 return Err(exitcode::CONFIG);
130 };
131 self.internal_topologies.push(topology);
132 Ok(())
133 }
134
135 #[cfg(feature = "api")]
137 pub fn setup_api(&self, handle: &Handle) -> Option<api::GrpcServer> {
138 if self.api.enabled {
139 let api_server = handle.block_on(api::GrpcServer::start(
141 self.topology.config(),
142 self.topology.watch(),
143 ));
144 match api_server {
145 Ok(server) => {
146 emit!(ApiStarted {
147 addr: server.addr()
148 });
149 Some(server)
150 }
151 Err(error) => {
152 let error = error.to_string();
153 error!(
154 message = "An error occurred that Vector couldn't handle.",
155 %error,
156 internal_log_rate_limit = false
157 );
158 _ = self
161 .topology
162 .abort_tx
163 .send(crate::signal::ShutdownError::ApiFailed { error });
164 None
165 }
166 }
167 } else {
168 info!(
169 message = "API is disabled, enable by setting `api.enabled` to `true` and use commands like `vector top`."
170 );
171 None
172 }
173 }
174}
175
176impl Application {
177 pub fn run(extra_context: ExtraContext) -> ExitStatus {
178 let (runtime, app) =
179 Self::prepare_start(extra_context).unwrap_or_else(|code| std::process::exit(code));
180
181 runtime.block_on(app.run())
182 }
183
184 pub fn prepare_start(
185 extra_context: ExtraContext,
186 ) -> Result<(Runtime, StartedApplication), ExitCode> {
187 Self::prepare(extra_context)
188 .and_then(|(runtime, app)| app.start(runtime.handle()).map(|app| (runtime, app)))
189 }
190
191 pub fn prepare(extra_context: ExtraContext) -> Result<(Runtime, Self), ExitCode> {
192 let opts = Opts::get_matches().map_err(|error| {
193 _ = error.print();
195 exitcode::USAGE
196 })?;
197
198 Self::prepare_from_opts(opts, extra_context)
199 }
200
201 pub fn prepare_from_opts(
202 opts: Opts,
203 extra_context: ExtraContext,
204 ) -> Result<(Runtime, Self), ExitCode> {
205 opts.root.init_global();
206
207 crate::sources::util::set_max_decompressed_size_bytes(
208 opts.root.max_decompressed_size_bytes,
209 );
210
211 let color = opts.root.color.use_color();
212
213 init_logging(
214 color,
215 opts.root.log_format,
216 opts.log_level(),
217 opts.root.internal_log_rate_limit,
218 opts.root.internal_logs_source_rate_limit,
219 );
220
221 #[cfg(unix)]
222 if opts.root.raise_fd_limit {
223 crate::cli::raise_file_descriptor_limit();
224 }
225
226 crate::set_global_color(color);
228
229 if opts.root.openssl_no_probe {
231 debug!(
232 message = "Disabled probing and configuration of root certificate locations on the system for OpenSSL."
233 );
234 }
235
236 let runtime = build_runtime(
237 opts.root.threads,
238 opts.root.chunk_size_events,
239 "vector-worker",
240 )?;
241
242 let mut signals = SignalPair::new(&runtime);
244
245 if let Some(sub_command) = &opts.sub_command {
246 config::set_env_var_interpolation(
248 opts.root.dangerously_allow_env_var_interpolation
249 || sub_command.dangerously_allow_env_var_interpolation(),
250 );
251 return Err(runtime.block_on(sub_command.execute(signals, color)));
252 }
253
254 config::set_env_var_interpolation(opts.root.dangerously_allow_env_var_interpolation);
255
256 let config = runtime.block_on(ApplicationConfig::from_opts(
257 &opts.root,
258 &mut signals.handler,
259 extra_context,
260 ))?;
261
262 Ok((
263 runtime,
264 Self {
265 root_opts: opts.root,
266 config,
267 signals,
268 },
269 ))
270 }
271
272 pub fn start(self, handle: &Handle) -> Result<StartedApplication, ExitCode> {
273 crate::trace::stop_early_buffering();
276
277 emit!(VectorStarted);
278 handle.spawn(heartbeat::heartbeat());
279
280 let Self {
281 root_opts,
282 config,
283 signals,
284 } = self;
285
286 #[cfg(feature = "api")]
287 let api_server = config.setup_api(handle);
288
289 let topology_controller = SharedTopologyController::new(TopologyController {
290 #[cfg(feature = "api")]
291 api_server,
292 topology: config.topology,
293 config_paths: config.config_paths.clone(),
294 require_healthy: root_opts.require_healthy,
295 extra_context: config.extra_context,
296 });
297
298 Ok(StartedApplication {
299 config_paths: config.config_paths,
300 internal_topologies: config.internal_topologies,
301 graceful_crash_receiver: config.graceful_crash_receiver,
302 signals,
303 topology_controller,
304 allow_empty_config: root_opts.allow_empty_config,
305 })
306 }
307}
308
309pub struct StartedApplication {
310 pub config_paths: Vec<ConfigPath>,
311 pub internal_topologies: Vec<RunningTopology>,
312 pub graceful_crash_receiver: ShutdownErrorReceiver,
313 pub signals: SignalPair,
314 pub topology_controller: SharedTopologyController,
315 pub allow_empty_config: bool,
316}
317
318impl StartedApplication {
319 pub async fn run(self) -> ExitStatus {
320 self.main().await.shutdown().await
321 }
322
323 pub async fn main(self) -> FinishedApplication {
324 let Self {
325 config_paths,
326 graceful_crash_receiver,
327 signals,
328 topology_controller,
329 internal_topologies,
330 allow_empty_config,
331 } = self;
332
333 let mut graceful_crash = UnboundedReceiverStream::new(graceful_crash_receiver);
334
335 let mut signal_handler = signals.handler;
336 let mut signal_rx = signals.receiver;
337
338 let signal = loop {
339 let has_sources = !topology_controller.lock().await.topology.config.is_empty();
340 tokio::select! {
341 signal = signal_rx.recv() => if let Some(signal) = handle_signal(
342 signal,
343 &topology_controller,
344 &config_paths,
345 &mut signal_handler,
346 allow_empty_config,
347 ).await {
348 break signal;
349 },
350 error = graceful_crash.next() => break SignalTo::Shutdown(error),
352 _ = TopologyController::sources_finished(topology_controller.clone()), if has_sources => {
353 info!("All sources have finished.");
354 break SignalTo::Shutdown(None)
355 } ,
356 else => unreachable!("Signal streams never end"),
357 }
358 };
359
360 FinishedApplication {
361 signal,
362 signal_rx,
363 topology_controller,
364 internal_topologies,
365 }
366 }
367}
368
369async fn handle_signal(
370 signal: Result<SignalTo, RecvError>,
371 topology_controller: &SharedTopologyController,
372 config_paths: &[ConfigPath],
373 signal_handler: &mut SignalHandler,
374 allow_empty_config: bool,
375) -> Option<SignalTo> {
376 match signal {
377 Ok(SignalTo::ReloadComponents(components_to_reload)) => {
378 let mut topology_controller = topology_controller.lock().await;
379 topology_controller
380 .topology
381 .extend_reload_set(components_to_reload);
382
383 if let Some(paths) = config::process_paths(config_paths) {
385 topology_controller.config_paths = paths;
386 }
387
388 let new_config = config::load_from_paths_with_provider_and_secrets(
390 &topology_controller.config_paths,
391 signal_handler,
392 allow_empty_config,
393 )
394 .await;
395
396 reload_config_from_result(topology_controller, new_config).await
397 }
398 Ok(SignalTo::ReloadFromConfigBuilder(config_builder)) => {
399 let topology_controller = topology_controller.lock().await;
400 reload_config_from_result(topology_controller, config_builder.build()).await
401 }
402 Ok(SignalTo::ReloadFromDisk) => {
403 let mut topology_controller = topology_controller.lock().await;
404
405 if let Some(paths) = config::process_paths(config_paths) {
407 topology_controller.config_paths = paths;
408 }
409
410 let new_config = config::load_from_paths_with_provider_and_secrets(
412 &topology_controller.config_paths,
413 signal_handler,
414 allow_empty_config,
415 )
416 .await;
417
418 if let Ok(ref config) = new_config {
419 let transform_keys_to_reload = config.transform_keys_with_external_files();
421
422 if !transform_keys_to_reload.is_empty() {
424 info!(
425 message = "Reloading transforms with external files.",
426 count = transform_keys_to_reload.len()
427 );
428 topology_controller
429 .topology
430 .extend_reload_set(transform_keys_to_reload);
431 }
432 }
433
434 reload_config_from_result(topology_controller, new_config).await
435 }
436 Ok(SignalTo::ReloadEnrichmentTables) => {
437 let topology_controller = topology_controller.lock().await;
438
439 topology_controller
440 .topology
441 .reload_enrichment_tables()
442 .await;
443 None
444 }
445 Err(RecvError::Lagged(amt)) => {
446 warn!("Overflow, dropped {} signals.", amt);
447 None
448 }
449 Err(RecvError::Closed) => Some(SignalTo::Shutdown(None)),
450 Ok(signal) => Some(signal),
451 }
452}
453
454async fn reload_config_from_result(
455 mut topology_controller: MutexGuard<'_, TopologyController>,
456 config: Result<Config, Vec<String>>,
457) -> Option<SignalTo> {
458 match config {
459 Ok(new_config) => match topology_controller.reload(new_config).await {
460 ReloadOutcome::FatalError(error) => Some(SignalTo::Shutdown(Some(error))),
461 _ => None,
462 },
463 Err(errors) => {
464 handle_config_errors(errors);
465 emit!(VectorConfigLoadError);
466 None
467 }
468 }
469}
470
471pub struct FinishedApplication {
472 pub signal: SignalTo,
473 pub signal_rx: SignalRx,
474 pub topology_controller: SharedTopologyController,
475 pub internal_topologies: Vec<RunningTopology>,
476}
477
478impl FinishedApplication {
479 pub async fn shutdown(self) -> ExitStatus {
480 let FinishedApplication {
481 signal,
482 signal_rx,
483 topology_controller,
484 internal_topologies,
485 } = self;
486
487 let topology_controller = topology_controller
490 .try_into_inner()
491 .expect("fail to unwrap topology controller")
492 .into_inner();
493
494 let status = match signal {
495 SignalTo::Shutdown(_) => Self::stop(topology_controller, signal_rx).await,
496 SignalTo::Quit => Self::quit(),
497 _ => unreachable!(),
498 };
499
500 for topology in internal_topologies {
501 topology.stop().await;
502 }
503
504 status
505 }
506
507 async fn stop(topology_controller: TopologyController, mut signal_rx: SignalRx) -> ExitStatus {
508 emit!(VectorStopping);
509 tokio::select! {
510 _ = topology_controller.stop() => {
511 emit!(VectorStopped);
512 ExitStatus::from_raw({
513 #[cfg(windows)]
514 {
515 exitcode::OK as u32
516 }
517 #[cfg(unix)]
518 exitcode::OK
519 })
520 }, _ = signal_rx.recv() => Self::quit(),
522 }
523 }
524
525 fn quit() -> ExitStatus {
526 emit!(VectorQuit);
528 ExitStatus::from_raw({
529 #[cfg(windows)]
530 {
531 exitcode::UNAVAILABLE as u32
532 }
533 #[cfg(unix)]
534 exitcode::OK
535 })
536 }
537}
538
539fn get_log_levels(default: &str) -> String {
540 std::env::var("VECTOR_LOG")
541 .or_else(|_| {
542 std::env::var("LOG").inspect(|_log| {
543 warn!(
544 message =
545 "DEPRECATED: Use of $LOG is deprecated. Please use $VECTOR_LOG instead."
546 );
547 })
548 })
549 .unwrap_or_else(|_| default.into())
550}
551
552pub fn build_runtime(
553 threads: Option<usize>,
554 chunk_size_events: Option<NonZeroUsize>,
555 thread_name: &str,
556) -> Result<Runtime, ExitCode> {
557 let mut rt_builder = runtime::Builder::new_multi_thread();
558 rt_builder.max_blocking_threads(20_000);
559 rt_builder.enable_all().thread_name(thread_name);
560
561 let threads = threads.unwrap_or_else(crate::num_threads);
562 if threads == 0 {
563 error!("The `threads` argument must be greater or equal to 1.");
564 return Err(exitcode::CONFIG);
565 }
566 WORKER_THREADS
567 .compare_exchange(0, threads, Ordering::Acquire, Ordering::Relaxed)
568 .unwrap_or_else(|_| panic!("double thread initialization"));
569 rt_builder.worker_threads(threads);
570
571 let chunk_size_events = chunk_size_events
572 .map(NonZeroUsize::get)
573 .unwrap_or(vector_lib::source_sender::DEFAULT_CHUNK_SIZE_EVENTS);
574
575 let Some(source_sender_buffer_size) = threads.checked_mul(chunk_size_events) else {
576 error!(
577 "The `chunk_size_events` argument is too large for the configured number of threads."
578 );
579 return Err(exitcode::CONFIG);
580 };
581 let Some(ready_array_capacity) =
582 chunk_size_events.checked_mul(crate::topology::builder::READY_ARRAY_CAPACITY_CHUNKS)
583 else {
584 error!("The `chunk_size_events` argument is too large.");
585 return Err(exitcode::CONFIG);
586 };
587
588 vector_lib::source_sender::set_chunk_size_events(chunk_size_events);
589 crate::topology::builder::set_source_sender_buffer_size(source_sender_buffer_size);
590 crate::topology::builder::set_ready_array_capacity(ready_array_capacity);
591
592 debug!(
593 message = "Building runtime.",
594 worker_threads = threads,
595 chunk_size_events
596 );
597 Ok(rt_builder.build().expect("Unable to create async runtime"))
598}
599
600pub async fn load_configs(
601 config_paths: &[ConfigPath],
602 watcher_conf: Option<config::watcher::WatcherConfig>,
603 require_healthy: Option<bool>,
604 allow_empty_config: bool,
605 graceful_shutdown_duration: Option<Duration>,
606 signal_handler: &mut SignalHandler,
607) -> Result<Config, ExitCode> {
608 let config_paths = config::process_paths(config_paths).ok_or(exitcode::CONFIG)?;
609
610 let watched_paths = config_paths
611 .iter()
612 .map(<&PathBuf>::from)
613 .collect::<Vec<_>>();
614
615 info!(
616 message = "Loading configs.",
617 paths = ?watched_paths
618 );
619
620 let mut config = config::load_from_paths_with_provider_and_secrets(
621 &config_paths,
622 signal_handler,
623 allow_empty_config,
624 )
625 .await
626 .map_err(handle_config_errors)?;
627
628 let mut watched_component_paths = Vec::new();
629
630 if let Some(watcher_conf) = watcher_conf {
631 for (name, transform) in config.transforms() {
632 let files = transform.inner.files_to_watch();
633 let component_config = ComponentConfig::new(
634 files.into_iter().cloned().collect(),
635 name.clone(),
636 ComponentType::Transform,
637 );
638 watched_component_paths.push(component_config);
639 }
640
641 for (name, sink) in config.sinks() {
642 let files = sink.inner.files_to_watch();
643 let component_config = ComponentConfig::new(
644 files.into_iter().cloned().collect(),
645 name.clone(),
646 ComponentType::Sink,
647 );
648 watched_component_paths.push(component_config);
649 }
650
651 for (name, table) in config.enrichment_tables() {
652 let files = table.inner.files_to_watch();
653 let component_config = ComponentConfig::new(
654 files.clone().into_iter().cloned().collect(),
655 name.clone(),
656 ComponentType::EnrichmentTable,
657 );
658 watched_component_paths.push(component_config);
659 if table.as_sink(name).is_some() {
660 let sink_component_config = ComponentConfig::new(
661 files.into_iter().cloned().collect(),
662 name.clone(),
663 ComponentType::Sink,
664 );
665 watched_component_paths.push(sink_component_config);
666 }
667 }
668
669 info!(
670 message = "Starting watcher.",
671 paths = ?watched_paths
672 );
673 info!(
674 message = "Components to watch.",
675 paths = ?watched_component_paths
676 );
677
678 config::watcher::spawn_thread(
680 watcher_conf,
681 signal_handler.clone_tx(),
682 watched_paths,
683 watched_component_paths,
684 None,
685 )
686 .map_err(|error| {
687 error!(message = "Unable to start config watcher.", %error);
688 exitcode::CONFIG
689 })?;
690 }
691
692 config::init_log_schema(config.global.log_schema.clone(), true);
693 config::init_telemetry(config.global.telemetry.clone(), true);
694
695 if !config.healthchecks.enabled {
696 info!("Health checks are disabled.");
697 }
698 config.healthchecks.set_require_healthy(require_healthy);
699 config.graceful_shutdown_duration = graceful_shutdown_duration;
700
701 Ok(config)
702}
703
704pub fn init_logging(
705 color: bool,
706 format: LogFormat,
707 log_level: &str,
708 internal_log_rate_limit_secs: u64,
709 internal_logs_source_rate_limit_secs: Option<NonZeroU64>,
710) {
711 let level = get_log_levels(log_level);
712 let json = match format {
713 LogFormat::Text => false,
714 LogFormat::Json => true,
715 };
716
717 trace::init(
718 color,
719 json,
720 &level,
721 internal_log_rate_limit_secs,
722 internal_logs_source_rate_limit_secs,
723 );
724 debug!(
725 message = "Internal log rate limit configured.",
726 internal_log_rate_limit_secs,
727 internal_logs_source_rate_limit_secs =
728 internal_logs_source_rate_limit_secs.map(NonZeroU64::get),
729 );
730 info!(message = "Log level is enabled.", ?level);
731}
732
733pub fn watcher_config(
734 method: WatchConfigMethod,
735 interval: NonZeroU64,
736) -> config::watcher::WatcherConfig {
737 match method {
738 WatchConfigMethod::Recommended => config::watcher::WatcherConfig::RecommendedWatcher,
739 WatchConfigMethod::Poll => config::watcher::WatcherConfig::PollWatcher(interval.into()),
740 }
741}