Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ tikv-jemallocator = { version = "0.6.0", default-features = false, features = ["

[target.'cfg(target_os = "linux")'.dependencies]
procfs = { version = "0.18.0", default-features = false }
sd-notify = { version = "0.5.0", default-features = false }

[build-dependencies]
prost-build = { workspace = true, optional = true }
Expand Down
1 change: 1 addition & 0 deletions LICENSE-3rdparty.csv
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,7 @@ schemars,https://github.com/GREsau/schemars,MIT,Graham Esau <gesau@hotmail.co.uk
scoped-tls,https://github.com/alexcrichton/scoped-tls,MIT OR Apache-2.0,Alex Crichton <alex@alexcrichton.com>
scopeguard,https://github.com/bluss/scopeguard,MIT OR Apache-2.0,bluss
sct,https://github.com/rustls/sct.rs,Apache-2.0 OR ISC OR MIT,Joseph Birr-Pixton <jpixton@gmail.com>
sd-notify,https://github.com/lnicola/sd-notify,MIT OR Apache-2.0,The sd-notify Authors
seahash,https://gitlab.redox-os.org/redox-os/seahash,MIT,"ticki <ticki@users.noreply.github.com>, Tom Almeida <tom@tommoa.me>"
sec1,https://github.com/RustCrypto/formats/tree/master/sec1,Apache-2.0 OR MIT,RustCrypto Developers
secrecy,https://github.com/iqlusioninc/crates/tree/main/secrecy,Apache-2.0 OR MIT,Tony Arcieri <tony@iqlusion.io>
Expand Down
4 changes: 4 additions & 0 deletions changelog.d/systemd_notify.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add systemd notify integration. Vector now sends `READY=1` when fully started, `STOPPING=1`
when beginning a graceful shutdown, and `WATCHDOG=1` pings at half the configured `WatchdogSec`
interval. The bundled `vector.service` and `hardened-vector.service` unit files are updated
to use `Type=notify`, with an optional `WatchdogSec` directive.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to add an authors: here like the other files

4 changes: 4 additions & 0 deletions distribution/systemd/hardened-vector.service
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ After=network-online.target
Requires=network-online.target

[Service]
Type=notify
EnvironmentFile=-/etc/default/vector
User=vector
Group=vector
Expand All @@ -20,6 +21,9 @@ ExecReload=/usr/bin/vector validate
ExecReload=/bin/kill -HUP $MAINPID
Restart=no

# Uncomment to enable watchdog. Vector will ping at half this interval.
#WatchdogSec=30

# capabilities
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
Expand Down
5 changes: 5 additions & 0 deletions distribution/systemd/vector.service
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ After=network-online.target
Requires=network-online.target

[Service]
Type=notify
User=vector
Group=vector
ExecStartPre=/usr/bin/vector validate
Expand All @@ -18,5 +19,9 @@ EnvironmentFile=-/etc/default/vector
# it is also supported to have it here.
StartLimitInterval=10
StartLimitBurst=5

# Uncomment to enable watchdog. Vector will ping at half this interval.
#WatchdogSec=30

[Install]
WantedBy=multi-user.target
7 changes: 7 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ impl Application {

emit!(VectorStarted);
handle.spawn(heartbeat::heartbeat());
#[cfg(target_os = "linux")]
handle.spawn(crate::systemd::watchdog());

let Self {
root_opts,
Expand All @@ -276,6 +278,9 @@ impl Application {
extra_context: config.extra_context,
});

#[cfg(target_os = "linux")]
crate::systemd::sd_notify_ready();

Ok(StartedApplication {
config_paths: config.config_paths,
internal_topologies: config.internal_topologies,
Expand Down Expand Up @@ -493,6 +498,8 @@ impl FinishedApplication {
}

async fn stop(topology_controller: TopologyController, mut signal_rx: SignalRx) -> ExitStatus {
#[cfg(target_os = "linux")]
crate::systemd::sd_notify_stopping();
emit!(VectorStopped);
tokio::select! {
_ = topology_controller.stop() => ExitStatus::from_raw({
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ pub(crate) mod sink_ext;
pub mod sinks;
#[allow(unreachable_pub)]
pub mod sources;
#[cfg(target_os = "linux")]
pub mod systemd;
#[cfg(feature = "api-client")]
#[allow(unreachable_pub)]
pub mod tap;
Expand Down
48 changes: 48 additions & 0 deletions src/systemd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//! Systemd integration via `sd_notify`
//! See <https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html>

use tokio::time::interval;

/// Sends `READY=1` to systemd via sd_notify. No-op if not Type=notify.
pub fn sd_notify_ready() {
if let Err(error) = sd_notify::notify(&[sd_notify::NotifyState::Ready]) {
warn!(message = "Failed to notify systemd of ready state.", %error);
}
}

/// Sends `STOPPING=1` to systemd via sd_notify. No-op if not Type=notify.
pub fn sd_notify_stopping() {
if let Err(error) = sd_notify::notify(&[sd_notify::NotifyState::Stopping]) {
warn!(message = "Failed to notify systemd of stopping state.", %error);
}
}

/// Sends `WATCHDOG=1` to systemd via sd_notify. No-op if not Type=notify.
pub fn sd_notify_watchdog() {
if let Err(error) = sd_notify::notify(&[sd_notify::NotifyState::Watchdog]) {
warn!(message = "Failed to send systemd watchdog ping.", %error);
}
}

/// Sends `WATCHDOG=1` pings at half the `WatchdogSec` interval. No-op if not set.
pub async fn watchdog() {
let Some(duration) = sd_notify::watchdog_enabled() else {
return;
};
let mut ticker = interval(duration / 2);
Copy link
Copy Markdown
Contributor

@jpds jpds Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also might be worth adding here:

    debug!(
        message = "Systemd watchdog keepalive started.",
        interval_secs = ticker.as_secs_f64(),
    );

loop {
ticker.tick().await;
sd_notify_watchdog();
}
}

#[cfg(test)]
mod tests {
#[test]
fn sd_notify_no_socket_does_not_panic() {
// NOTIFY_SOCKET is not set in test environments - these must be no-ops.
super::sd_notify_ready();
super::sd_notify_stopping();
super::sd_notify_watchdog();
}
}
Loading